Files
dspace-angular/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts

342 lines
12 KiB
TypeScript

import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
import { Component, Inject, OnInit, OnDestroy, Input } 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';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../../core/browse/browse.service';
import { BrowseEntry } from '../../core/shared/browse-entry.model';
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 { 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';
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
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'
})
/**
* Component for browsing (items) by metadata definition.
* A metadata definition (a.k.a. browse id) is a short term used to describe one
* or multiple metadata fields. An example would be 'author' for
* 'dc.contributor.*'
*/
@rendersBrowseBy(BrowseByDataType.Metadata)
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
/**
* The optional context
*/
@Input() context: Context;
/**
* The {@link BrowseByDataType} of this Component
*/
@Input() browseByType: BrowseByDataType;
/**
* 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 logo of the current Community or Collection
*/
logo$: Observable<RemoteData<Bitstream>>;
/**
* The pagination config used to display the values
*/
paginationConfig: PaginationComponentOptions;
/**
* The pagination observable
*/
currentPagination$: Observable<PaginationComponentOptions>;
/**
* The sorting config observable
*/
currentSort$: Observable<SortOptions>;
/**
* List of subscriptions
*/
subs: Subscription[] = [];
/**
* The default browse id to resort to when none is provided
*/
defaultBrowseId = 'author';
/**
* The current browse id
*/
browseId = this.defaultBrowseId;
/**
* The type of StartsWith options to render
* Defaults to text
*/
startsWithType = StartsWithType.text;
/**
* The list of StartsWith options
* Should be defined after ngOnInit is called!
*/
startsWithOptions;
/**
* The value we're browsing 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 = '';
/**
* The authority key (may be undefined) associated with {@link #value}.
*/
authority: string;
/**
* The current startsWith option (fetched and updated from query-params)
*/
startsWith: string;
/**
* Determines whether to request embedded thumbnail.
*/
fetchThumbnails: boolean;
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,
) {
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
id: BBM_PAGINATION_ID,
currentPage: 1,
pageSize: this.appConfig.browseBy.pageSize,
});
}
ngOnInit(): void {
const sortConfig = new SortOptions('default', SortDirection.ASC);
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
this.subs.push(
observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe(
map(([routeParams, queryParams, currentPage, currentSort]) => {
return [Object.assign({}, routeParams, queryParams),currentPage,currentSort];
})
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
this.browseId = params.id || this.defaultBrowseId;
this.authority = params.authority;
if (typeof params.value === 'string'){
this.value = params.value.trim();
} else {
this.value = '';
}
if (params.startsWith === undefined || params.startsWith === '') {
this.startsWith = undefined;
}
if (typeof params.startsWith === 'string'){
this.startsWith = params.startsWith.trim();
}
if (isNotEmpty(this.value)) {
this.updatePageWithItems(
browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
} else {
this.updatePage(browseParamsToOptions(params, currentPage, currentSort, this.browseId, false));
}
this.updateParent(params.scope);
this.updateLogo();
}));
this.updateStartsWithTextOptions();
}
/**
* Update the StartsWith options with text values
* It adds the value "0-9" as well as all letters from A to Z
*/
updateStartsWithTextOptions() {
this.startsWithOptions = ['0-9', ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')];
}
/**
* 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, 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()
);
}
}
/**
* 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
*/
goPrev() {
if (this.items$) {
this.items$.pipe(getFirstSucceededRemoteData()).subscribe((items) => {
this.items$ = this.browseService.getPrevBrowseItems(items);
});
} else if (this.browseEntries$) {
this.browseEntries$.pipe(getFirstSucceededRemoteData()).subscribe((entries) => {
this.browseEntries$ = this.browseService.getPrevBrowseEntries(entries);
});
}
}
/**
* Navigate to the next page
*/
goNext() {
if (this.items$) {
this.items$.pipe(getFirstSucceededRemoteData()).subscribe((items) => {
this.items$ = this.browseService.getNextBrowseItems(items);
});
} else if (this.browseEntries$) {
this.browseEntries$.pipe(getFirstSucceededRemoteData()).subscribe((entries) => {
this.browseEntries$ = this.browseService.getNextBrowseEntries(entries);
});
}
}
ngOnDestroy(): void {
this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe());
this.paginationService.clearPagination(this.paginationConfig.id);
}
}
/**
* Creates browse entry search options.
* @param defaultBrowseId the metadata definition to fetch entries or items for
* @param paginationConfig the required pagination configuration
* @param sortConfig the required sort configuration
* @param fetchThumbnails optional boolean for fetching thumbnails
* @returns BrowseEntrySearchOptions instance
*/
export function getBrowseSearchOptions(defaultBrowseId: string,
paginationConfig: PaginationComponentOptions,
sortConfig: SortOptions,
fetchThumbnails?: boolean) {
if (!hasValue(fetchThumbnails)) {
fetchThumbnails = false;
}
return new BrowseEntrySearchOptions(defaultBrowseId, paginationConfig, sortConfig, null,
null, fetchThumbnails);
}
/**
* 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
* @param fetchThumbnail Optional parameter for requesting thumbnail images
*/
export function browseParamsToOptions(params: any,
paginationConfig: PaginationComponentOptions,
sortConfig: SortOptions,
metadata?: string,
fetchThumbnail?: boolean): BrowseEntrySearchOptions {
return new BrowseEntrySearchOptions(
metadata,
paginationConfig,
sortConfig,
params.startsWith,
params.scope,
fetchThumbnail
);
}