mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'remotes/origin/master' into submission
# Conflicts: # src/app/core/data/browse-entries-response-parsing.service.spec.ts # src/app/core/data/browse-entries-response-parsing.service.ts
This commit is contained in:
@@ -342,7 +342,21 @@
|
||||
}
|
||||
},
|
||||
"browse": {
|
||||
"title": "Browsing {{ collection }} by {{ field }} {{ value }}"
|
||||
"title": "Browsing {{ collection }} by {{ field }} {{ value }}",
|
||||
"metadata": {
|
||||
"title": "Title",
|
||||
"author": "Author",
|
||||
"subject": "Subject"
|
||||
},
|
||||
"comcol": {
|
||||
"head": "Browse",
|
||||
"by": {
|
||||
"title": "By Title",
|
||||
"author": "By Author",
|
||||
"subject": "By Subject"
|
||||
}
|
||||
},
|
||||
"empty": "No items to show."
|
||||
},
|
||||
"admin": {
|
||||
"registries": {
|
||||
@@ -439,6 +453,7 @@
|
||||
"browse_global_by_issue_date": "By Issue Date",
|
||||
"browse_global_by_author": "By Author",
|
||||
"browse_global_by_title": "By Title",
|
||||
"browse_global_by_subject": "By Subject",
|
||||
"statistics": "Statistics",
|
||||
"browse_community": "This Community",
|
||||
"browse_community_by_issue_date": "By Issue Date",
|
||||
|
@@ -1,11 +0,0 @@
|
||||
<div class="container">
|
||||
<div class="browse-by-author w-100 row">
|
||||
<ds-browse-by class="col-xs-12 w-100"
|
||||
title="{{'browse.title' | translate:{collection: '', field: 'Author', value: (value)? '"' + value + '"': ''} }}"
|
||||
[objects$]="(items$ !== undefined)? items$ : authors$"
|
||||
[currentUrl]="currentUrl"
|
||||
[paginationConfig]="paginationConfig"
|
||||
[sortConfig]="sortConfig">
|
||||
</ds-browse-by>
|
||||
</div>
|
||||
</div>
|
@@ -1,107 +0,0 @@
|
||||
|
||||
import {combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { ItemDataService } from '../../core/data/item-data.service';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-author-page',
|
||||
styleUrls: ['./browse-by-author-page.component.scss'],
|
||||
templateUrl: './browse-by-author-page.component.html'
|
||||
})
|
||||
/**
|
||||
* Component for browsing (items) by author (dc.contributor.author)
|
||||
*/
|
||||
export class BrowseByAuthorPageComponent implements OnInit {
|
||||
|
||||
authors$: Observable<RemoteData<PaginatedList<BrowseEntry>>>;
|
||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'browse-by-author-pagination',
|
||||
currentPage: 1,
|
||||
pageSize: 20
|
||||
});
|
||||
sortConfig: SortOptions = new SortOptions('dc.contributor.author', SortDirection.ASC);
|
||||
subs: Subscription[] = [];
|
||||
currentUrl: string;
|
||||
value = '';
|
||||
|
||||
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute, private browseService: BrowseService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currentUrl = this.route.snapshot.pathFromRoot
|
||||
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
|
||||
.join('/');
|
||||
this.updatePage({
|
||||
pagination: this.paginationConfig,
|
||||
sort: this.sortConfig
|
||||
});
|
||||
this.subs.push(
|
||||
observableCombineLatest(
|
||||
this.route.params,
|
||||
this.route.queryParams,
|
||||
(params, queryParams, ) => {
|
||||
return Object.assign({}, params, queryParams);
|
||||
})
|
||||
.subscribe((params) => {
|
||||
const page = +params.page || this.paginationConfig.currentPage;
|
||||
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
||||
const sortDirection = params.sortDirection || this.sortConfig.direction;
|
||||
const sortField = params.sortField || this.sortConfig.field;
|
||||
this.value = +params.value || params.value || '';
|
||||
const pagination = Object.assign({},
|
||||
this.paginationConfig,
|
||||
{ currentPage: page, pageSize: pageSize }
|
||||
);
|
||||
const sort = Object.assign({},
|
||||
this.sortConfig,
|
||||
{ direction: sortDirection, field: sortField }
|
||||
);
|
||||
const searchOptions = {
|
||||
pagination: pagination,
|
||||
sort: sort
|
||||
};
|
||||
if (isNotEmpty(this.value)) {
|
||||
this.updatePageWithItems(searchOptions, this.value);
|
||||
} else {
|
||||
this.updatePage(searchOptions);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current page with searchOptions
|
||||
* @param searchOptions Options to narrow down your search:
|
||||
* { pagination: PaginationComponentOptions,
|
||||
* sort: SortOptions }
|
||||
*/
|
||||
updatePage(searchOptions) {
|
||||
this.authors$ = this.browseService.getBrowseEntriesFor('author', searchOptions);
|
||||
this.items$ = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current page with searchOptions and display items linked to author
|
||||
* @param searchOptions Options to narrow down your search:
|
||||
* { pagination: PaginationComponentOptions,
|
||||
* sort: SortOptions }
|
||||
* @param author The author's name for displaying items
|
||||
*/
|
||||
updatePageWithItems(searchOptions, author: string) {
|
||||
this.items$ = this.browseService.getBrowseItemsFor('author', author, searchOptions);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
<div class="container">
|
||||
<div class="browse-by-metadata w-100 row">
|
||||
<ds-browse-by class="col-xs-12 w-100"
|
||||
title="{{'browse.title' | translate:{collection: (parent$ | async)?.payload?.name || '', field: 'browse.metadata.' + metadata | translate, value: (value)? '"' + value + '"': ''} }}"
|
||||
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
|
||||
[paginationConfig]="paginationConfig"
|
||||
[sortConfig]="sortConfig">
|
||||
</ds-browse-by>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,162 @@
|
||||
import { BrowseByMetadataPageComponent, browseParamsToOptions } from './browse-by-metadata-page.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
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 { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
|
||||
describe('BrowseByMetadataPageComponent', () => {
|
||||
let comp: BrowseByMetadataPageComponent;
|
||||
let fixture: ComponentFixture<BrowseByMetadataPageComponent>;
|
||||
let browseService: BrowseService;
|
||||
let route: ActivatedRoute;
|
||||
|
||||
const mockCommunity = Object.assign(new Community(), {
|
||||
id: 'test-uuid',
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
value: 'test community'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const mockEntries = [
|
||||
{
|
||||
type: 'author',
|
||||
authority: null,
|
||||
value: 'John Doe',
|
||||
language: 'en',
|
||||
count: 1
|
||||
},
|
||||
{
|
||||
type: 'author',
|
||||
authority: null,
|
||||
value: 'James Doe',
|
||||
language: 'en',
|
||||
count: 3
|
||||
},
|
||||
{
|
||||
type: 'subject',
|
||||
authority: null,
|
||||
value: 'Fake subject',
|
||||
language: 'en',
|
||||
count: 2
|
||||
}
|
||||
];
|
||||
|
||||
const mockItems = [
|
||||
Object.assign(new Item(), {
|
||||
id: 'fakeId'
|
||||
})
|
||||
];
|
||||
|
||||
const mockBrowseService = {
|
||||
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData(mockEntries.filter((entry) => entry.type === options.metadataDefinition)),
|
||||
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData(mockItems)
|
||||
};
|
||||
|
||||
const mockDsoService = {
|
||||
findById: () => observableOf(new RemoteData(false, false, true, null, mockCommunity))
|
||||
};
|
||||
|
||||
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
||||
params: observableOf({})
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||
declarations: [BrowseByMetadataPageComponent, EnumKeysPipe],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: BrowseService, useValue: mockBrowseService },
|
||||
{ provide: DSpaceObjectDataService, useValue: mockDsoService }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BrowseByMetadataPageComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
browseService = (comp as any).browseService;
|
||||
route = (comp as any).route;
|
||||
route.params = observableOf({});
|
||||
comp.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should fetch the correct entries depending on the metadata definition', () => {
|
||||
comp.browseEntries$.subscribe((result) => {
|
||||
expect(result.payload.page).toEqual(mockEntries.filter((entry) => entry.type === 'author'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fetch any items when no value is provided', () => {
|
||||
expect(comp.items$).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('when a value is provided', () => {
|
||||
beforeEach(() => {
|
||||
const paramsWithValue = {
|
||||
metadata: 'author',
|
||||
value: 'John Doe'
|
||||
};
|
||||
|
||||
route.params = observableOf(paramsWithValue);
|
||||
comp.ngOnInit();
|
||||
});
|
||||
|
||||
it('should fetch items', () => {
|
||||
comp.items$.subscribe((result) => {
|
||||
expect(result.payload.page).toEqual(mockItems);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('when calling browseParamsToOptions', () => {
|
||||
let result: BrowseEntrySearchOptions;
|
||||
|
||||
beforeEach(() => {
|
||||
const paramsWithPaginationAndScope = {
|
||||
page: 5,
|
||||
pageSize: 10,
|
||||
sortDirection: SortDirection.ASC,
|
||||
sortField: 'fake-field',
|
||||
scope: 'fake-scope'
|
||||
};
|
||||
|
||||
result = browseParamsToOptions(paramsWithPaginationAndScope, Object.assign({}), Object.assign({}), 'author');
|
||||
});
|
||||
|
||||
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
||||
expect(result.metadataDefinition).toEqual('author');
|
||||
expect(result.pagination.currentPage).toEqual(5);
|
||||
expect(result.pagination.pageSize).toEqual(10);
|
||||
expect(result.sort.direction).toEqual(SortDirection.ASC);
|
||||
expect(result.sort.field).toEqual('fake-field');
|
||||
expect(result.scope).toEqual('fake-scope');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {
|
||||
return observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), objects)));
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
import {combineLatest as observableCombineLatest, merge as observableMerge, Observable, Subscription } from 'rxjs';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { getSucceededRemoteData } from '../../core/shared/operators';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-metadata-page',
|
||||
styleUrls: ['./browse-by-metadata-page.component.scss'],
|
||||
templateUrl: './browse-by-metadata-page.component.html'
|
||||
})
|
||||
/**
|
||||
* Component for browsing (items) by metadata definition
|
||||
* A metadata definition is a short term used to describe one or multiple metadata fields.
|
||||
* An example would be 'author' for 'dc.contributor.*'
|
||||
*/
|
||||
export class BrowseByMetadataPageComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The list of browse-entries to display
|
||||
*/
|
||||
browseEntries$: Observable<RemoteData<PaginatedList<BrowseEntry>>>;
|
||||
|
||||
/**
|
||||
* The list of items to display when a value is present
|
||||
*/
|
||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||
|
||||
/**
|
||||
* The current Community or Collection we're browsing metadata/items in
|
||||
*/
|
||||
parent$: Observable<RemoteData<DSpaceObject>>;
|
||||
|
||||
/**
|
||||
* The pagination config used to display the values
|
||||
*/
|
||||
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'browse-by-metadata-pagination',
|
||||
currentPage: 1,
|
||||
pageSize: 20
|
||||
});
|
||||
|
||||
/**
|
||||
* The sorting config used to sort the values (defaults to Ascending)
|
||||
*/
|
||||
sortConfig: SortOptions = new SortOptions('default', SortDirection.ASC);
|
||||
|
||||
/**
|
||||
* List of subscriptions
|
||||
*/
|
||||
subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* The default metadata definition to resort to when none is provided
|
||||
*/
|
||||
defaultMetadata = 'author';
|
||||
|
||||
/**
|
||||
* The current metadata definition
|
||||
*/
|
||||
metadata = this.defaultMetadata;
|
||||
|
||||
/**
|
||||
* The value we're browing items for
|
||||
* - When the value is not empty, we're browsing items
|
||||
* - When the value is empty, we're browsing browse-entries (values for the given metadata definition)
|
||||
*/
|
||||
value = '';
|
||||
|
||||
public constructor(private route: ActivatedRoute,
|
||||
private browseService: BrowseService,
|
||||
private dsoService: DSpaceObjectDataService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.updatePage(new BrowseEntrySearchOptions(null, this.paginationConfig, this.sortConfig));
|
||||
this.subs.push(
|
||||
observableCombineLatest(
|
||||
this.route.params,
|
||||
this.route.queryParams,
|
||||
(params, queryParams, ) => {
|
||||
return Object.assign({}, params, queryParams);
|
||||
})
|
||||
.subscribe((params) => {
|
||||
this.metadata = params.metadata || this.defaultMetadata;
|
||||
this.value = +params.value || params.value || '';
|
||||
const searchOptions = browseParamsToOptions(params, this.paginationConfig, this.sortConfig, this.metadata);
|
||||
if (isNotEmpty(this.value)) {
|
||||
this.updatePageWithItems(searchOptions, this.value);
|
||||
} else {
|
||||
this.updatePage(searchOptions);
|
||||
}
|
||||
this.updateParent(params.scope);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current page with searchOptions
|
||||
* @param searchOptions Options to narrow down your search:
|
||||
* { metadata: string
|
||||
* pagination: PaginationComponentOptions,
|
||||
* sort: SortOptions,
|
||||
* scope: string }
|
||||
*/
|
||||
updatePage(searchOptions: BrowseEntrySearchOptions) {
|
||||
this.browseEntries$ = this.browseService.getBrowseEntriesFor(searchOptions);
|
||||
this.items$ = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current page with searchOptions and display items linked to the given value
|
||||
* @param searchOptions Options to narrow down your search:
|
||||
* { metadata: string
|
||||
* pagination: PaginationComponentOptions,
|
||||
* sort: SortOptions,
|
||||
* scope: string }
|
||||
* @param value The value of the browse-entry to display items for
|
||||
*/
|
||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string) {
|
||||
this.items$ = this.browseService.getBrowseItemsFor(value, 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)) {
|
||||
this.parent$ = this.dsoService.findById(scope).pipe(
|
||||
getSucceededRemoteData()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
||||
* @param params URL and query parameters
|
||||
* @param paginationConfig Pagination configuration
|
||||
* @param sortConfig Sorting configuration
|
||||
* @param metadata Optional metadata definition to fetch browse entries/items for
|
||||
*/
|
||||
export function browseParamsToOptions(params: any,
|
||||
paginationConfig: PaginationComponentOptions,
|
||||
sortConfig: SortOptions,
|
||||
metadata?: string): BrowseEntrySearchOptions {
|
||||
return new BrowseEntrySearchOptions(
|
||||
metadata,
|
||||
Object.assign({},
|
||||
paginationConfig,
|
||||
{
|
||||
currentPage: +params.page || paginationConfig.currentPage,
|
||||
pageSize: +params.pageSize || paginationConfig.pageSize
|
||||
}
|
||||
),
|
||||
Object.assign({},
|
||||
sortConfig,
|
||||
{
|
||||
direction: params.sortDirection || sortConfig.direction,
|
||||
field: params.sortField || sortConfig.field
|
||||
}
|
||||
),
|
||||
params.scope
|
||||
);
|
||||
}
|
@@ -1,9 +1,8 @@
|
||||
<div class="container">
|
||||
<div class="browse-by-title w-100 row">
|
||||
<ds-browse-by class="col-xs-12 w-100"
|
||||
title="{{'browse.title' | translate:{collection: '', field: 'Title', value: ''} }}"
|
||||
title="{{'browse.title' | translate:{collection: (parent$ | async)?.payload?.name || '', field: 'browse.metadata.title' | translate, value: ''} }}"
|
||||
[objects$]="items$"
|
||||
[currentUrl]="currentUrl"
|
||||
[paginationConfig]="paginationConfig"
|
||||
[sortConfig]="sortConfig">
|
||||
</ds-browse-by>
|
||||
|
@@ -0,0 +1,85 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
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 { 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 { ItemDataService } from '../../core/data/item-data.service';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
|
||||
describe('BrowseByTitlePageComponent', () => {
|
||||
let comp: BrowseByTitlePageComponent;
|
||||
let fixture: ComponentFixture<BrowseByTitlePageComponent>;
|
||||
let itemDataService: ItemDataService;
|
||||
let route: ActivatedRoute;
|
||||
|
||||
const mockCommunity = Object.assign(new Community(), {
|
||||
id: 'test-uuid',
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
value: 'test community'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const mockItems = [
|
||||
Object.assign(new Item(), {
|
||||
id: 'fakeId',
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
value: 'Fake Title'
|
||||
}
|
||||
]
|
||||
})
|
||||
];
|
||||
|
||||
const mockItemDataService = {
|
||||
findAll: () => toRemoteData(mockItems)
|
||||
};
|
||||
|
||||
const mockDsoService = {
|
||||
findById: () => observableOf(new RemoteData(false, false, true, null, mockCommunity))
|
||||
};
|
||||
|
||||
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
||||
params: observableOf({})
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||
declarations: [BrowseByTitlePageComponent, EnumKeysPipe],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: ItemDataService, useValue: mockItemDataService },
|
||||
{ provide: DSpaceObjectDataService, useValue: mockDsoService }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BrowseByTitlePageComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
itemDataService = (comp as any).itemDataService;
|
||||
route = (comp as any).route;
|
||||
});
|
||||
|
||||
it('should initialize the list of items', () => {
|
||||
comp.items$.subscribe((result) => {
|
||||
expect(result.payload.page).toEqual(mockItems);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,16 +1,20 @@
|
||||
|
||||
import {combineLatest as observableCombineLatest, Observable , Subscription } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, merge as observableMerge, Observable, Subscription } from 'rxjs';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { ItemDataService } from '../../core/data/item-data.service';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ActivatedRoute, PRIMARY_OUTLET, UrlSegmentGroup } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { browseParamsToOptions } from '../+browse-by-metadata-page/browse-by-metadata-page.component';
|
||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { getSucceededRemoteData } from '../../core/shared/operators';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-title-page',
|
||||
@@ -22,28 +26,44 @@ import { Collection } from '../../core/shared/collection.model';
|
||||
*/
|
||||
export class BrowseByTitlePageComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The list of items to display
|
||||
*/
|
||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||
|
||||
/**
|
||||
* The current Community or Collection we're browsing metadata/items in
|
||||
*/
|
||||
parent$: Observable<RemoteData<DSpaceObject>>;
|
||||
|
||||
/**
|
||||
* The pagination configuration to use for displaying the list of items
|
||||
*/
|
||||
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'browse-by-title-pagination',
|
||||
currentPage: 1,
|
||||
pageSize: 20
|
||||
});
|
||||
sortConfig: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
|
||||
subs: Subscription[] = [];
|
||||
currentUrl: string;
|
||||
|
||||
public constructor(private itemDataService: ItemDataService, private route: ActivatedRoute) {
|
||||
/**
|
||||
* The sorting configuration to use for displaying the list of items
|
||||
* Sorted by title (Ascending by default)
|
||||
*/
|
||||
sortConfig: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
|
||||
|
||||
/**
|
||||
* List of subscriptions
|
||||
*/
|
||||
subs: Subscription[] = [];
|
||||
|
||||
public constructor(private itemDataService: ItemDataService,
|
||||
private route: ActivatedRoute,
|
||||
private dsoService: DSpaceObjectDataService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currentUrl = this.route.snapshot.pathFromRoot
|
||||
.map((snapshot) => (snapshot.routeConfig) ? snapshot.routeConfig.path : '')
|
||||
.join('/');
|
||||
this.updatePage({
|
||||
pagination: this.paginationConfig,
|
||||
sort: this.sortConfig
|
||||
});
|
||||
this.updatePage(new BrowseEntrySearchOptions(null, this.paginationConfig, this.sortConfig));
|
||||
this.subs.push(
|
||||
observableCombineLatest(
|
||||
this.route.params,
|
||||
@@ -52,22 +72,8 @@ export class BrowseByTitlePageComponent implements OnInit {
|
||||
return Object.assign({}, params, queryParams);
|
||||
})
|
||||
.subscribe((params) => {
|
||||
const page = +params.page || this.paginationConfig.currentPage;
|
||||
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
||||
const sortDirection = params.sortDirection || this.sortConfig.direction;
|
||||
const sortField = params.sortField || this.sortConfig.field;
|
||||
const pagination = Object.assign({},
|
||||
this.paginationConfig,
|
||||
{ currentPage: page, pageSize: pageSize }
|
||||
);
|
||||
const sort = Object.assign({},
|
||||
this.sortConfig,
|
||||
{ direction: sortDirection, field: sortField }
|
||||
);
|
||||
this.updatePage({
|
||||
pagination: pagination,
|
||||
sort: sort
|
||||
});
|
||||
this.updatePage(browseParamsToOptions(params, this.paginationConfig, this.sortConfig));
|
||||
this.updateParent(params.scope)
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -77,14 +83,27 @@ export class BrowseByTitlePageComponent implements OnInit {
|
||||
* { pagination: PaginationComponentOptions,
|
||||
* sort: SortOptions }
|
||||
*/
|
||||
updatePage(searchOptions) {
|
||||
updatePage(searchOptions: BrowseEntrySearchOptions) {
|
||||
this.items$ = this.itemDataService.findAll({
|
||||
currentPage: searchOptions.pagination.currentPage,
|
||||
elementsPerPage: searchOptions.pagination.pageSize,
|
||||
sort: searchOptions.sort
|
||||
sort: searchOptions.sort,
|
||||
scopeID: searchOptions.scope
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
this.parent$ = this.dsoService.findById(scope).pipe(
|
||||
getSucceededRemoteData()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
|
||||
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
|
||||
import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'title', component: BrowseByTitlePageComponent },
|
||||
{ path: 'author', component: BrowseByAuthorPageComponent }
|
||||
{ path: ':metadata', component: BrowseByMetadataPageComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
|
@@ -4,8 +4,8 @@ import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-ti
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { BrowseByRoutingModule } from './browse-by-routing.module';
|
||||
import { BrowseByAuthorPageComponent } from './+browse-by-author-page/browse-by-author-page.component';
|
||||
import { BrowseService } from '../core/browse/browse.service';
|
||||
import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -15,7 +15,7 @@ import { BrowseService } from '../core/browse/browse.service';
|
||||
],
|
||||
declarations: [
|
||||
BrowseByTitlePageComponent,
|
||||
BrowseByAuthorPageComponent
|
||||
BrowseByMetadataPageComponent
|
||||
],
|
||||
providers: [
|
||||
ItemDataService,
|
||||
|
@@ -7,6 +7,8 @@
|
||||
<ds-comcol-page-header
|
||||
[name]="collection.name">
|
||||
</ds-comcol-page-header>
|
||||
<!-- Browse-By Links -->
|
||||
<ds-comcol-page-browse-by [id]="collection.id"></ds-comcol-page-browse-by>
|
||||
<!-- Collection logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||
[logo]="(logoRD$ | async)?.payload"
|
||||
|
@@ -3,6 +3,8 @@
|
||||
<div *ngIf="communityRD?.payload; let communityPayload">
|
||||
<!-- Community name -->
|
||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||
<!-- Browse-By Links -->
|
||||
<ds-comcol-page-browse-by [id]="communityPayload.id"></ds-comcol-page-browse-by>
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||
[logo]="(logoRD$ | async)?.payload"
|
||||
|
17
src/app/core/browse/browse-entry-search-options.model.ts
Normal file
17
src/app/core/browse/browse-entry-search-options.model.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortOptions } from '../cache/models/sort-options.model';
|
||||
|
||||
/**
|
||||
* A class that defines the search options to be used for fetching browse entries or items
|
||||
* - metadataDefinition: The metadata definition to fetch entries or items for
|
||||
* - pagination: Optional pagination options to use
|
||||
* - sort: Optional sorting options to use
|
||||
* - scope: An optional scope to limit the results within a specific collection or community
|
||||
*/
|
||||
export class BrowseEntrySearchOptions {
|
||||
constructor(public metadataDefinition: string,
|
||||
public pagination?: PaginationComponentOptions,
|
||||
public sort?: SortOptions,
|
||||
public scope?: string) {
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest } from
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||
import { BrowseService } from './browse.service';
|
||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||
import { RequestEntry } from '../data/request.reducer';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
@@ -151,14 +152,14 @@ describe('BrowseService', () => {
|
||||
it('should configure a new BrowseEntriesRequest', () => {
|
||||
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries);
|
||||
|
||||
scheduler.schedule(() => service.getBrowseEntriesFor(browseDefinitions[1].id).subscribe());
|
||||
scheduler.schedule(() => service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected, undefined);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
service.getBrowseEntriesFor(browseDefinitions[1].id);
|
||||
service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id));
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
|
||||
@@ -170,14 +171,14 @@ describe('BrowseService', () => {
|
||||
it('should configure a new BrowseItemsRequest', () => {
|
||||
const expected = new BrowseItemsRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items + '?filterValue=' + mockAuthorName);
|
||||
|
||||
scheduler.schedule(() => service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName).subscribe());
|
||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected, undefined);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName);
|
||||
service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id));
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
|
||||
@@ -191,7 +192,7 @@ describe('BrowseService', () => {
|
||||
const definitionID = 'invalidID';
|
||||
const expected = cold('--#-', undefined, new Error(`No metadata browse definition could be found for id '${definitionID}'`));
|
||||
|
||||
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected);
|
||||
expect(service.getBrowseEntriesFor(new BrowseEntrySearchOptions(definitionID))).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,7 +202,7 @@ describe('BrowseService', () => {
|
||||
const definitionID = 'invalidID';
|
||||
const expected = cold('--#-', undefined, new Error(`No metadata browse definition could be found for id '${definitionID}'`))
|
||||
|
||||
expect(service.getBrowseItemsFor(definitionID, mockAuthorName)).toBeObservable(expected);
|
||||
expect(service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(definitionID))).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -33,6 +33,7 @@ import {
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||
|
||||
@Injectable()
|
||||
export class BrowseService {
|
||||
@@ -80,18 +81,18 @@ export class BrowseService {
|
||||
return this.rdb.toRemoteDataObservable(requestEntry$, payload$);
|
||||
}
|
||||
|
||||
getBrowseEntriesFor(definitionID: string, options: {
|
||||
pagination?: PaginationComponentOptions;
|
||||
sort?: SortOptions;
|
||||
} = {}): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
||||
getBrowseEntriesFor(options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
||||
const request$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(definitionID),
|
||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||
hasValueOperator(),
|
||||
map((_links: any) => _links.entries),
|
||||
hasValueOperator(),
|
||||
map((href: string) => {
|
||||
// TODO nearly identical to PaginatedSearchOptions => refactor
|
||||
const args = [];
|
||||
if (isNotEmpty(options.sort)) {
|
||||
args.push(`scope=${options.scope}`);
|
||||
}
|
||||
if (isNotEmpty(options.sort)) {
|
||||
args.push(`sort=${options.sort.field},${options.sort.direction}`);
|
||||
}
|
||||
@@ -133,17 +134,17 @@ export class BrowseService {
|
||||
* sort: SortOptions }
|
||||
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
||||
*/
|
||||
getBrowseItemsFor(definitionID: string, filterValue: string, options: {
|
||||
pagination?: PaginationComponentOptions;
|
||||
sort?: SortOptions;
|
||||
} = {}): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
getBrowseItemsFor(filterValue: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
const request$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(definitionID),
|
||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||
hasValueOperator(),
|
||||
map((_links: any) => _links.items),
|
||||
hasValueOperator(),
|
||||
map((href: string) => {
|
||||
const args = [];
|
||||
if (isNotEmpty(options.sort)) {
|
||||
args.push(`scope=${options.scope}`);
|
||||
}
|
||||
if (isNotEmpty(options.sort)) {
|
||||
args.push(`sort=${options.sort.field},${options.sort.direction}`);
|
||||
}
|
||||
|
@@ -106,21 +106,6 @@ describe('BrowseEntriesResponseParsingService', () => {
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
const invalidResponseNotAList = {
|
||||
payload: {
|
||||
authority: null,
|
||||
value: 'Arulmozhiyal, Ramaswamy',
|
||||
valueLang: null,
|
||||
count: 1,
|
||||
type: 'browseEntry',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/discover/browses/author/entries'
|
||||
},
|
||||
items: {
|
||||
href: 'https://rest.api/discover/browses/author/items?filterValue=Arulmozhiyal, Ramaswamy'
|
||||
}
|
||||
},
|
||||
},
|
||||
statusCode: 200,
|
||||
statusText: 'OK'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
@@ -30,11 +30,13 @@ export class BrowseEntriesResponseParsingService extends BaseResponseParsingServ
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._embedded)
|
||||
&& Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
|
||||
const serializer = new DSpaceRESTv2Serializer(BrowseEntry);
|
||||
const browseEntries = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
|
||||
return new GenericSuccessResponse(browseEntries, data.statusCode, data.statusText, this.processPageInfo(data.payload));
|
||||
if (isNotEmpty(data.payload)) {
|
||||
let browseEntries = [];
|
||||
if (isNotEmpty(data.payload._embedded) && Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
|
||||
const serializer = new DSpaceRESTv2Serializer(BrowseEntry);
|
||||
browseEntries = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
|
||||
}
|
||||
return new GenericSuccessResponse(browseEntries, data.statusCode, this.processPageInfo(data.payload));
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
|
@@ -28,7 +28,13 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
const processRequestDTO = this.process<NormalizedObject, ResourceType>(data.payload, request.uuid);
|
||||
let processRequestDTO;
|
||||
// Prevent empty pages returning an error, initialize empty array instead.
|
||||
if (hasValue(data.payload) && hasValue(data.payload.page) && data.payload.page.totalElements === 0) {
|
||||
processRequestDTO = { page: [] };
|
||||
} else {
|
||||
processRequestDTO = this.process<NormalizedObject, ResourceType>(data.payload, request.uuid);
|
||||
}
|
||||
let objectList = processRequestDTO;
|
||||
|
||||
if (hasNoValue(processRequestDTO)) {
|
||||
|
@@ -51,28 +51,18 @@ export class NavbarComponent extends MenuComponent implements OnInit {
|
||||
} as TextMenuItemModel,
|
||||
index: 0
|
||||
},
|
||||
// {
|
||||
// id: 'browse_global_communities_and_collections',
|
||||
// parentID: 'browse_global',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.browse_global_communities_and_collections',
|
||||
// link: '#'
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
{
|
||||
id: 'browse_global_communities_and_collections',
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.browse_global_communities_and_collections',
|
||||
link: '#'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'browse_global_global_by_issue_date',
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.browse_global_by_issue_date',
|
||||
link: '#'
|
||||
} as LinkMenuItemModel,
|
||||
}, {
|
||||
id: 'browse_global_global_by_title',
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
@@ -94,6 +84,17 @@ export class NavbarComponent extends MenuComponent implements OnInit {
|
||||
link: '/browse/author'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'browse_global_by_subject',
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.browse_global_by_subject',
|
||||
link: '/browse/subject'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
|
||||
/* Statistics */
|
||||
{
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<ng-container *ngVar="(objects$ | async) as objects">
|
||||
<h2 class="w-100">{{title}}</h2>
|
||||
<h2 class="w-100">{{title | translate}}</h2>
|
||||
<div *ngIf="objects?.hasSucceeded && !objects?.isLoading && objects?.payload?.page.length > 0" @fadeIn>
|
||||
<ds-viewable-collection
|
||||
[config]="paginationConfig"
|
||||
@@ -7,6 +7,9 @@
|
||||
[objects]="objects">
|
||||
</ds-viewable-collection>
|
||||
</div>
|
||||
<ds-loading *ngIf="!objects || objects?.payload?.page.length <= 0" message="{{'loading.browse-by' | translate}}"></ds-loading>
|
||||
<ds-loading *ngIf="!objects || objects?.isLoading" message="{{'loading.browse-by' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="objects?.hasFailed" message="{{'error.browse-by' | translate}}"></ds-error>
|
||||
<div *ngIf="!objects?.isLoading && objects?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
||||
{{'browse.empty' | translate}}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@@ -5,7 +5,6 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
|
||||
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { fadeIn, fadeInOut } from '../animations/fade';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||
|
||||
@Component({
|
||||
@@ -21,10 +20,23 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode
|
||||
* Component to display a browse-by page for any ListableObject
|
||||
*/
|
||||
export class BrowseByComponent {
|
||||
/**
|
||||
* The i18n message to display as title
|
||||
*/
|
||||
@Input() title: string;
|
||||
|
||||
/**
|
||||
* The list of objects to display
|
||||
*/
|
||||
@Input() objects$: Observable<RemoteData<PaginatedList<ListableObject>>>;
|
||||
|
||||
/**
|
||||
* The pagination configuration used for the list
|
||||
*/
|
||||
@Input() paginationConfig: PaginationComponentOptions;
|
||||
|
||||
/**
|
||||
* The sorting configuration used for the list
|
||||
*/
|
||||
@Input() sortConfig: SortOptions;
|
||||
@Input() currentUrl: string;
|
||||
query: string;
|
||||
}
|
||||
|
@@ -0,0 +1,6 @@
|
||||
<h3>{{'browse.comcol.head' | translate}}</h3>
|
||||
<ul>
|
||||
<li><a [routerLink]="['/browse/title']" [queryParams]="{scope: id}">{{'browse.comcol.by.title' | translate}}</a></li>
|
||||
<li><a [routerLink]="['/browse/author']" [queryParams]="{scope: id}">{{'browse.comcol.by.author' | translate}}</a></li>
|
||||
<li><a [routerLink]="['/browse/subject']" [queryParams]="{scope: id}">{{'browse.comcol.by.subject' | translate}}</a></li>
|
||||
</ul>
|
@@ -0,0 +1,16 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
/**
|
||||
* A component to display the "Browse By" section of a Community or Collection page
|
||||
* It expects the ID of the Community or Collection as input to be passed on as a scope
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-comcol-page-browse-by',
|
||||
templateUrl: './comcol-page-browse-by.component.html',
|
||||
})
|
||||
export class ComcolPageBrowseByComponent {
|
||||
/**
|
||||
* The ID of the Community or Collection
|
||||
*/
|
||||
@Input() id: string;
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<div *ngIf="content" class="content-with-optional-title">
|
||||
<div *ngIf="content" class="content-with-optional-title mb-2">
|
||||
<h2 *ngIf="title">{{ title | translate }}</h2>
|
||||
<div *ngIf="hasInnerHtml" [innerHtml]="content"></div>
|
||||
<div *ngIf="!hasInnerHtml">{{content}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<div class="d-flex flex-row">
|
||||
<a [routerLink]="" [queryParams]="{value: object.value}" class="lead">
|
||||
<a [routerLink]="" [queryParams]="{value: object.value}" [queryParamsHandling]="'merge'" class="lead">
|
||||
{{object.value}}
|
||||
</a>
|
||||
<span class="pr-2"> </span>
|
||||
|
@@ -94,6 +94,7 @@ import { CreateComColPageComponent } from './comcol-forms/create-comcol-page/cre
|
||||
import { EditComColPageComponent } from './comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { DeleteComColPageComponent } from './comcol-forms/delete-comcol-page/delete-comcol-page.component';
|
||||
import { LangSwitchComponent } from './lang-switch/lang-switch.component';
|
||||
import { ComcolPageBrowseByComponent } from './comcol-page-browse-by/comcol-page-browse-by.component';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -143,6 +144,7 @@ const COMPONENTS = [
|
||||
CreateComColPageComponent,
|
||||
EditComColPageComponent,
|
||||
DeleteComColPageComponent,
|
||||
ComcolPageBrowseByComponent,
|
||||
DsDynamicFormComponent,
|
||||
DsDynamicFormControlContainerComponent,
|
||||
DsDynamicListComponent,
|
||||
|
Reference in New Issue
Block a user