mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-09 11:03:05 +00:00
refactored items, bundles and bitstreams, test builders
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
||||
import {
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getRemoteDataPayload,
|
||||
getSucceededRemoteData,
|
||||
toDSpaceObjectListRD
|
||||
} from '../../../core/shared/operators';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
@@ -81,6 +87,7 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
private searchService: SearchService,
|
||||
private notificationsService: NotificationsService,
|
||||
private itemDataService: ItemDataService,
|
||||
private collectionDataService: CollectionDataService,
|
||||
private translateService: TranslateService) {
|
||||
}
|
||||
|
||||
@@ -106,7 +113,8 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
);
|
||||
|
||||
const owningCollectionRD$ = this.itemRD$.pipe(
|
||||
switchMap((itemRD: RemoteData<Item>) => itemRD.payload.owningCollection)
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((item: Item) => this.collectionDataService.findOwningCollectionFor(item))
|
||||
);
|
||||
const itemCollectionsAndOptions$ = observableCombineLatest(
|
||||
this.itemCollectionsRD$,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<ds-metadata-field-wrapper *ngIf="hasSucceeded() | async" [label]="label | translate">
|
||||
<ds-metadata-field-wrapper *ngIf="(this.collectionsRD$ | async)?.hasSucceeded" [label]="label | translate">
|
||||
<div class="collections">
|
||||
<a *ngFor="let collection of (collections | async); let last=last;" [routerLink]="['/collections', collection.id]">
|
||||
<a *ngFor="let collection of (this.collectionsRD$ | async)?.payload?.page; let last=last;" [routerLink]="['/collections', collection.id]">
|
||||
<span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -1,12 +1,13 @@
|
||||
|
||||
import {map} from 'rxjs/operators';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
|
||||
/**
|
||||
* This component renders the parent collections section of the item
|
||||
@@ -25,9 +26,9 @@ export class CollectionsComponent implements OnInit {
|
||||
|
||||
separator = '<br/>';
|
||||
|
||||
collections: Observable<Collection[]>;
|
||||
collectionsRD$: Observable<RemoteData<PaginatedList<Collection>>>;
|
||||
|
||||
constructor(private rdbs: RemoteDataBuildService) {
|
||||
constructor(private cds: CollectionDataService) {
|
||||
|
||||
}
|
||||
|
||||
@@ -37,11 +38,25 @@ export class CollectionsComponent implements OnInit {
|
||||
// TODO: this should use parents, but the collections
|
||||
// for an Item aren't returned by the REST API yet,
|
||||
// only the owning collection
|
||||
this.collections = this.item.owner.pipe(map((rd: RemoteData<Collection>) => [rd.payload]));
|
||||
this.collectionsRD$ = this.cds.findOwningCollectionFor(this.item).pipe(
|
||||
map((rd: RemoteData<Collection>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
return new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
new PaginatedList({
|
||||
elementsPerPage: 10,
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
totalElements: 1
|
||||
} as PageInfo, [rd.payload])
|
||||
);
|
||||
} else {
|
||||
return rd as any;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
hasSucceeded() {
|
||||
return this.item.owner.pipe(map((rd: RemoteData<Collection>) => rd.hasSucceeded));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<div class="file-section row" *ngFor="let file of (bitstreamsObs | async); let last=last;">
|
||||
<div class="file-section row" *ngFor="let file of (bitstreams$ | async); let last=last;">
|
||||
<div class="col-3">
|
||||
<ds-thumbnail [thumbnail]="thumbnails.get(file.id) | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<dl class="row">
|
||||
@@ -21,7 +21,7 @@
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<a [href]="file.content" [download]="file.name">
|
||||
<a [href]="file._links.content.href" [download]="file.name">
|
||||
{{"item.page.filesection.download" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { getBitstreamBuilder } from '../../../../core/cache/builders/bitstream-builder';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
|
||||
import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* This component renders the file section of the item
|
||||
@@ -22,27 +25,42 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
|
||||
|
||||
label: string;
|
||||
|
||||
bitstreamsObs: Observable<Bitstream[]>;
|
||||
bitstreams$: Observable<Bitstream[]>;
|
||||
|
||||
thumbnails: Map<string, Observable<Bitstream>> = new Map();
|
||||
constructor(
|
||||
bitstreamDataService: BitstreamDataService,
|
||||
private parentInjector: Injector
|
||||
) {
|
||||
super(bitstreamDataService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
const originals = this.item.getFiles();
|
||||
const licenses = this.item.getBitstreamsByBundleName('LICENSE');
|
||||
this.bitstreamsObs = observableCombineLatest(originals, licenses).pipe(map(([o, l]) => [...o, ...l]));
|
||||
this.bitstreamsObs.subscribe(
|
||||
(files) =>
|
||||
files.forEach(
|
||||
// TODO pagination
|
||||
const originals$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL', { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
|
||||
getFirstSucceededRemoteListPayload(),
|
||||
startWith([])
|
||||
);
|
||||
const licenses$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'LICENSE', { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
|
||||
getFirstSucceededRemoteListPayload(),
|
||||
startWith([])
|
||||
);
|
||||
this.bitstreams$ = observableCombineLatest(originals$, licenses$).pipe(
|
||||
map(([o, l]) => [...o, ...l]),
|
||||
map((files: Bitstream[]) =>
|
||||
files.map(
|
||||
(original) => {
|
||||
const thumbnail: Observable<Bitstream> = this.item.getThumbnailForOriginal(original);
|
||||
this.thumbnails.set(original.id, thumbnail);
|
||||
return getBitstreamBuilder(this.parentInjector, original)
|
||||
.loadThumbnail(this.item)
|
||||
.loadBitstreamFormat()
|
||||
.build();
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<ng-container *ngVar="(bitstreamsObs | async) as bitstreams">
|
||||
<ng-container *ngVar="(bitstreams$ | async) as bitstreams">
|
||||
<ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate">
|
||||
<div class="file-section">
|
||||
<a *ngFor="let file of bitstreams; let last=last;" [href]="file?.content" [download]="file?.name">
|
||||
<a *ngFor="let file of bitstreams; let last=last;" [href]="file?._links.content.href" [download]="file?.name">
|
||||
<span>{{file?.name}}</span>
|
||||
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
|
||||
<span *ngIf="!last" innerHTML="{{separator}}"></span>
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
|
||||
|
||||
/**
|
||||
* This component renders the file section of the item
|
||||
@@ -20,14 +22,21 @@ export class FileSectionComponent implements OnInit {
|
||||
|
||||
separator = '<br/>';
|
||||
|
||||
bitstreamsObs: Observable<Bitstream[]>;
|
||||
bitstreams$: Observable<Bitstream[]>;
|
||||
|
||||
constructor(
|
||||
protected bitstreamDataService: BitstreamDataService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this.bitstreamsObs = this.item.getFiles();
|
||||
this.bitstreams$ = this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL').pipe(
|
||||
getFirstSucceededRemoteListPayload()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item',
|
||||
@@ -10,4 +14,14 @@ import { Item } from '../../../../core/shared/item.model';
|
||||
*/
|
||||
export class ItemComponent {
|
||||
@Input() object: Item;
|
||||
|
||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
||||
}
|
||||
|
||||
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
return this.bitstreamDataService.getThumbnailFor(this.object).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
60
src/app/core/cache/builders/bitstream-builder.ts
vendored
Normal file
60
src/app/core/cache/builders/bitstream-builder.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { BitstreamDataService } from '../../data/bitstream-data.service';
|
||||
import { BitstreamFormatDataService } from '../../data/bitstream-format-data.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { BitstreamFormat } from '../../shared/bitstream-format.model';
|
||||
import { Bitstream } from '../../shared/bitstream.model';
|
||||
import { Item } from '../../shared/item.model';
|
||||
|
||||
export const getBitstreamBuilder = (parentInjector: Injector, bitstream: Bitstream) => {
|
||||
const injector = Injector.create({
|
||||
providers:[
|
||||
{
|
||||
provide: BitstreamBuilder,
|
||||
useClass: BitstreamBuilder,
|
||||
deps:[
|
||||
BitstreamDataService,
|
||||
BitstreamFormatDataService,
|
||||
]
|
||||
}
|
||||
],
|
||||
parent: parentInjector
|
||||
});
|
||||
return injector.get(BitstreamBuilder).initWithBitstream(bitstream);
|
||||
};
|
||||
|
||||
export class BitstreamBuilder {
|
||||
private bitstream: Bitstream;
|
||||
private thumbnail: Observable<RemoteData<Bitstream>>;
|
||||
private format: Observable<RemoteData<BitstreamFormat>>;
|
||||
|
||||
constructor(
|
||||
protected bitstreamDataService: BitstreamDataService,
|
||||
protected bitstreamFormatDataService: BitstreamFormatDataService
|
||||
) {
|
||||
}
|
||||
|
||||
initWithBitstream(bitstream: Bitstream): BitstreamBuilder {
|
||||
this.bitstream = bitstream;
|
||||
return this;
|
||||
}
|
||||
|
||||
loadThumbnail(item: Item): BitstreamBuilder {
|
||||
this.thumbnail = this.bitstreamDataService.getMatchingThumbnail(item, this.bitstream);
|
||||
return this;
|
||||
}
|
||||
|
||||
loadBitstreamFormat(): BitstreamBuilder {
|
||||
this.format = this.bitstreamFormatDataService.findByBitstream(this.bitstream);
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): Bitstream {
|
||||
const bitstream = this.bitstream;
|
||||
bitstream.thumbnail = this.thumbnail;
|
||||
bitstream.format = this.format;
|
||||
return bitstream;
|
||||
}
|
||||
|
||||
}
|
@@ -53,7 +53,7 @@ export function getMapsToType(type: string | ResourceType) {
|
||||
return typeMap.get(type);
|
||||
}
|
||||
|
||||
export function relationship<T extends CacheableObject>(value: GenericConstructor<T>, isList: boolean = false): any {
|
||||
export function relationship<T extends CacheableObject>(value: GenericConstructor<T>, isList: boolean = false, shouldAutoResolve: boolean = true): any {
|
||||
return function r(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
if (!target || !propertyKey) {
|
||||
return;
|
||||
@@ -66,7 +66,8 @@ export function relationship<T extends CacheableObject>(value: GenericConstructo
|
||||
relationshipMap.set(target.constructor, metaDataList);
|
||||
return Reflect.metadata(relationshipKey, {
|
||||
resourceType: (value as any).type.value,
|
||||
isList
|
||||
isList,
|
||||
shouldAutoResolve
|
||||
}).apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
@@ -1,9 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf, race as observableRace } from 'rxjs';
|
||||
import {
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
race as observableRace
|
||||
} from 'rxjs';
|
||||
import { distinctUntilChanged, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { hasValue, hasValueOperator, isEmpty, isNotEmpty, isNotUndefined } from '../../../shared/empty.util';
|
||||
import {
|
||||
hasNoValue,
|
||||
hasValue,
|
||||
hasValueOperator,
|
||||
isEmpty,
|
||||
isNotEmpty,
|
||||
isNotUndefined
|
||||
} from '../../../shared/empty.util';
|
||||
import { PaginatedList } from '../../data/paginated-list';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RemoteDataError } from '../../data/remote-data-error';
|
||||
@@ -157,46 +169,55 @@ export class RemoteDataBuildService {
|
||||
relationships.forEach((relationship: string) => {
|
||||
let result;
|
||||
if (hasValue(normalized[relationship])) {
|
||||
const { resourceType, isList } = getRelationMetadata(normalized, relationship);
|
||||
const { resourceType, isList, shouldAutoResolve } = getRelationMetadata(normalized, relationship);
|
||||
const objectList = normalized[relationship].page || normalized[relationship];
|
||||
if (typeof objectList !== 'string') {
|
||||
objectList.forEach((href: string) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
if (shouldAutoResolve) {
|
||||
if (typeof objectList !== 'string') {
|
||||
objectList.forEach((href: string) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
if (!this.requestService.isCachedOrPending(request)) {
|
||||
this.requestService.configure(request)
|
||||
}
|
||||
});
|
||||
|
||||
const rdArr = [];
|
||||
objectList.forEach((href: string) => {
|
||||
rdArr.push(this.buildSingle(href));
|
||||
});
|
||||
|
||||
if (isList) {
|
||||
result = this.aggregate(rdArr);
|
||||
} else if (rdArr.length === 1) {
|
||||
result = rdArr[0];
|
||||
}
|
||||
} else {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), objectList);
|
||||
if (!this.requestService.isCachedOrPending(request)) {
|
||||
this.requestService.configure(request)
|
||||
}
|
||||
});
|
||||
|
||||
const rdArr = [];
|
||||
objectList.forEach((href: string) => {
|
||||
rdArr.push(this.buildSingle(href));
|
||||
});
|
||||
|
||||
if (isList) {
|
||||
result = this.aggregate(rdArr);
|
||||
} else if (rdArr.length === 1) {
|
||||
result = rdArr[0];
|
||||
}
|
||||
} else {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), objectList);
|
||||
if (!this.requestService.isCachedOrPending(request)) {
|
||||
this.requestService.configure(request)
|
||||
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
|
||||
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
|
||||
// but it should still be built as a list
|
||||
if (isList) {
|
||||
result = this.buildList(objectList);
|
||||
} else {
|
||||
result = this.buildSingle(objectList);
|
||||
}
|
||||
}
|
||||
|
||||
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
|
||||
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
|
||||
// but it should still be built as a list
|
||||
if (isList) {
|
||||
result = this.buildList(objectList);
|
||||
if (hasValue(normalized[relationship].page)) {
|
||||
links[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
|
||||
} else {
|
||||
result = this.buildSingle(objectList);
|
||||
links[relationship] = result;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasValue(normalized[relationship].page)) {
|
||||
links[relationship] = this.toPaginatedList(result, normalized[relationship].pageInfo);
|
||||
} else {
|
||||
links[relationship] = result;
|
||||
if (hasNoValue(links._links)) {
|
||||
links._links = {};
|
||||
}
|
||||
links._links[relationship] = {
|
||||
href: objectList
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -22,13 +22,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
|
||||
* The relative path to this Bitstream's file
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(Bitstream, false, false)
|
||||
content: string;
|
||||
|
||||
/**
|
||||
* The format of this Bitstream
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(BitstreamFormat, false)
|
||||
@relationship(BitstreamFormat, false, false)
|
||||
format: string;
|
||||
|
||||
/**
|
||||
@@ -41,14 +42,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject<Bitstream> {
|
||||
* An array of Bundles that are direct parents of this Bitstream
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(Item, true)
|
||||
@relationship(Item, true, false)
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Bundle that owns this Bitstream
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(Item, false)
|
||||
@relationship(Item, false, false)
|
||||
owner: string;
|
||||
|
||||
/**
|
||||
|
@@ -22,7 +22,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject<Bundle> {
|
||||
* The primary bitstream of this Bundle
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(Bitstream, false)
|
||||
@relationship(Bitstream, false, false)
|
||||
primaryBitstream: string;
|
||||
|
||||
/**
|
||||
@@ -39,7 +39,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject<Bundle> {
|
||||
* List of Bitstreams that are part of this Bundle
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(Bitstream, true)
|
||||
@relationship(Bitstream, true, false)
|
||||
bitstreams: string[];
|
||||
|
||||
}
|
||||
|
@@ -30,42 +30,41 @@ export class NormalizedCollection extends NormalizedDSpaceObject<Collection> {
|
||||
* The Bitstream that represents the license of this Collection
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(License, false)
|
||||
license: string;
|
||||
|
||||
/**
|
||||
* The Bitstream that represents the default Access Conditions of this Collection
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourcePolicy, false)
|
||||
@relationship(ResourcePolicy, false, false)
|
||||
defaultAccessConditions: string;
|
||||
|
||||
/**
|
||||
* The Bitstream that represents the logo of this Collection
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Bitstream, false)
|
||||
@relationship(Bitstream, false, false)
|
||||
logo: string;
|
||||
|
||||
/**
|
||||
* An array of Communities that are direct parents of this Collection
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Community, true)
|
||||
@relationship(Community, true, false)
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Community that owns this Collection
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Community, false)
|
||||
@relationship(Community, false, false)
|
||||
owner: string;
|
||||
|
||||
/**
|
||||
* List of Items that are part of (not necessarily owned by) this Collection
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Item, true)
|
||||
@relationship(Item, true, false)
|
||||
items: string[];
|
||||
|
||||
}
|
||||
|
@@ -48,25 +48,25 @@ export class NormalizedItem extends NormalizedDSpaceObject<Item> {
|
||||
* An array of Collections that are direct parents of this Item
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Collection, true)
|
||||
@relationship(Collection, true, false)
|
||||
parents: string[];
|
||||
|
||||
/**
|
||||
* The Collection that owns this Item
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Collection, false)
|
||||
@relationship(Collection, false, false)
|
||||
owningCollection: string;
|
||||
|
||||
/**
|
||||
* List of Bitstreams that are owned by this Item
|
||||
*/
|
||||
@deserialize
|
||||
@relationship(Bundle, true)
|
||||
@relationship(Bundle, true, false)
|
||||
bundles: string[];
|
||||
|
||||
@deserialize
|
||||
@relationship(Relationship, true)
|
||||
@relationship(Relationship, true, false)
|
||||
relationships: string[];
|
||||
|
||||
}
|
||||
|
@@ -27,24 +27,26 @@ export abstract class BaseResponseParsingService {
|
||||
return this.processArray(data, request);
|
||||
} else if (isRestDataObject(data)) {
|
||||
const object = this.deserialize(data);
|
||||
if (isNotEmpty(data._embedded)) {
|
||||
Object
|
||||
.keys(data._embedded)
|
||||
.filter((property) => data._embedded.hasOwnProperty(property))
|
||||
.forEach((property) => {
|
||||
const parsedObj = this.process<ObjectDomain>(data._embedded[property], request);
|
||||
if (isNotEmpty(parsedObj)) {
|
||||
if (isRestPaginatedList(data._embedded[property])) {
|
||||
object[property] = parsedObj;
|
||||
object[property].page = parsedObj.page.map((obj) => this.retrieveObjectOrUrl(obj));
|
||||
} else if (isRestDataObject(data._embedded[property])) {
|
||||
object[property] = this.retrieveObjectOrUrl(parsedObj);
|
||||
} else if (Array.isArray(parsedObj)) {
|
||||
object[property] = parsedObj.map((obj) => this.retrieveObjectOrUrl(obj))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
// if (isNotEmpty(data._embedded)) {
|
||||
// Object
|
||||
// .keys(data._embedded)
|
||||
// .filter((property) => data._embedded.hasOwnProperty(property))
|
||||
// .forEach((property) => {
|
||||
// const parsedObj = this.process<ObjectDomain>(data._embedded[property], request);
|
||||
// if (isNotEmpty(parsedObj)) {
|
||||
// if (isRestPaginatedList(data._embedded[property])) {
|
||||
// object[property] = parsedObj;
|
||||
// object[property].page = parsedObj.page.map((obj) => this.retrieveObjectOrUrl(obj));
|
||||
// } else if (isRestDataObject(data._embedded[property])) {
|
||||
// object[property] = this.retrieveObjectOrUrl(parsedObj);
|
||||
// } else if (Array.isArray(parsedObj)) {
|
||||
// object[property] = parsedObj.map((obj) => this.retrieveObjectOrUrl(obj))
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
this.cache(object, request);
|
||||
return object;
|
||||
|
147
src/app/core/data/bitstream-data.service.ts
Normal file
147
src/app/core/data/bitstream-data.service.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { Bundle } from '../shared/bundle.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { BundleDataService } from './bundle-data.service';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
import { DataService } from './data.service';
|
||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { PaginatedList } from './paginated-list';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { RemoteDataError } from './remote-data-error';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BitstreamDataService extends DataService<Bitstream> {
|
||||
|
||||
protected linkPath = 'bitstreams';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected dataBuildService: NormalizedObjectBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected cds: CommunityDataService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DSOChangeAnalyzer<Bitstream>,
|
||||
protected bundleService: BundleDataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string> {
|
||||
// TODO needed? if not, perhaps remove it from datasevice?
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the bitstreams in a given bundle
|
||||
*
|
||||
* @param bundle the bundle to retrieve bitstreams from
|
||||
* @param options options for the find all request
|
||||
*/
|
||||
findAllByBundle(bundle: Bundle, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
return this.findAllByHref(bundle._links.bitstreams.href, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail for the given item
|
||||
* @returns {Observable<RemoteData<Bitstream>>} the first bitstream in the THUMBNAIL bundle
|
||||
*/
|
||||
// TODO should be implemented rest side. Item should get a thumbnail link
|
||||
public getThumbnailFor(item: Item): Observable<RemoteData<Bitstream>> {
|
||||
return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
|
||||
switchMap((bundleRD: RemoteData<Bundle>) => {
|
||||
if (hasValue(bundleRD.payload)) {
|
||||
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: 1 }).pipe(
|
||||
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
|
||||
if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
|
||||
return new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
bitstreamRD.payload.page[0]
|
||||
);
|
||||
} else {
|
||||
return bitstreamRD as any;
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return [bundleRD as any];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// TODO should be implemented rest side
|
||||
public getMatchingThumbnail(item: Item, bitstreamInOriginal: Bitstream): Observable<RemoteData<Bitstream>> {
|
||||
return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
|
||||
switchMap((bundleRD: RemoteData<Bundle>) => {
|
||||
if (hasValue(bundleRD.payload)) {
|
||||
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
|
||||
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
|
||||
if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
|
||||
const matchingThumbnail = bitstreamRD.payload.page.find((thumbnail: Bitstream) =>
|
||||
thumbnail.name.startsWith(bitstreamInOriginal.name)
|
||||
);
|
||||
if (hasValue(matchingThumbnail)) {
|
||||
return new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
matchingThumbnail
|
||||
);
|
||||
} else {
|
||||
return new RemoteData(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
new RemoteDataError(404, '404', 'No matching thumbnail found'),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return bitstreamRD as any;
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return [bundleRD as any];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
return this.bundleService.findByItemAndName(item, bundleName).pipe(
|
||||
switchMap((bundleRD: RemoteData<Bundle>) => {
|
||||
if (hasValue(bundleRD.payload)) {
|
||||
return this.findAllByBundle(bundleRD.payload, options);
|
||||
} else {
|
||||
return [bundleRD as any];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { DataService } from './data.service';
|
||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||
@@ -183,4 +185,8 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
|
||||
map((request: RequestEntry) => request.response.isSuccessful)
|
||||
);
|
||||
}
|
||||
|
||||
findByBitstream(bitstream: Bitstream): Observable<RemoteData<BitstreamFormat>> {
|
||||
return this.findByHref(bitstream._links.format.href);
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +1,31 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DataService } from './data.service';
|
||||
import { Bundle } from '../shared/bundle.model';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { Bundle } from '../shared/bundle.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { BitstreamDataService } from './bitstream-data.service';
|
||||
import { DataService } from './data.service';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { PaginatedList } from './paginated-list';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
|
||||
/**
|
||||
* A service responsible for fetching/sending data from/to the REST API on the bundles endpoint
|
||||
*/
|
||||
@Injectable()
|
||||
@Injectable(
|
||||
{providedIn: 'root'}
|
||||
)
|
||||
export class BundleDataService extends DataService<Bundle> {
|
||||
protected linkPath = 'bundles';
|
||||
protected forceBypassCache = false;
|
||||
@@ -43,4 +51,29 @@ export class BundleDataService extends DataService<Bundle> {
|
||||
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath);
|
||||
}
|
||||
|
||||
findAllByItem(item: Item, options?: FindListOptions): Observable<RemoteData<PaginatedList<Bundle>>> {
|
||||
return this.findAllByHref(item._links.bundles.href, options);
|
||||
}
|
||||
|
||||
// TODO should be implemented rest side
|
||||
findByItemAndName(item: Item, bundleName: string): Observable<RemoteData<Bundle>> {
|
||||
return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
|
||||
map((rd: RemoteData<PaginatedList<Bundle>>) => {
|
||||
if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
|
||||
const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
|
||||
bundle.name === bundleName);
|
||||
return new RemoteData(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
matchingBundle
|
||||
);
|
||||
} else {
|
||||
return rd as any;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { ComColDataService } from './comcol-data.service';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
import { RequestService } from './request.service';
|
||||
@@ -240,4 +241,9 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
this.halService.getEndpoint('collections', `${communityEndpointHref}/${parentUUID}`)),
|
||||
);
|
||||
}
|
||||
|
||||
findOwningCollectionFor(item: Item): Observable<RemoteData<Collection>> {
|
||||
return this.findByHref(item._links.owningCollection.href);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -81,7 +81,9 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
|
||||
protected abstract getFindByParentHref(parentUUID: string): Observable<string>;
|
||||
|
||||
public findByParent(parentUUID: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<T>>> {
|
||||
const href$ = this.buildHrefFromFindOptions(this.getFindByParentHref(parentUUID), [], options);
|
||||
const href$ = this.getFindByParentHref(parentUUID).pipe(
|
||||
map((href: string) => this.buildHrefFromFindOptions(href, options))
|
||||
);
|
||||
return this.findList(href$, options);
|
||||
}
|
||||
|
||||
|
@@ -76,12 +76,12 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
* Return an observable that emits created HREF
|
||||
*/
|
||||
protected getFindAllHref(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||
let result: Observable<string>;
|
||||
let result$: Observable<string>;
|
||||
const args = [];
|
||||
|
||||
result = this.getBrowseEndpoint(options, linkPath).pipe(distinctUntilChanged());
|
||||
result$ = this.getBrowseEndpoint(options, linkPath).pipe(distinctUntilChanged());
|
||||
|
||||
return this.buildHrefFromFindOptions(result, args, options);
|
||||
return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,10 +93,10 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
* Return an observable that emits created HREF
|
||||
*/
|
||||
protected getSearchByHref(searchMethod: string, options: FindListOptions = {}): Observable<string> {
|
||||
let result: Observable<string>;
|
||||
let result$: Observable<string>;
|
||||
const args = [];
|
||||
|
||||
result = this.getSearchEndpoint(searchMethod);
|
||||
result$ = this.getSearchEndpoint(searchMethod);
|
||||
|
||||
if (hasValue(options.searchParams)) {
|
||||
options.searchParams.forEach((param: SearchParam) => {
|
||||
@@ -104,37 +104,39 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
})
|
||||
}
|
||||
|
||||
return this.buildHrefFromFindOptions(result, args, options);
|
||||
return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn an options object into a query string and combine it with the given HREF
|
||||
*
|
||||
* @param href$ The HREF to which the query string should be appended
|
||||
* @param args Array with additional params to combine with query string
|
||||
* @param href The HREF to which the query string should be appended
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param extraArgs Array with additional params to combine with query string
|
||||
* @return {Observable<string>}
|
||||
* Return an observable that emits created HREF
|
||||
*/
|
||||
protected buildHrefFromFindOptions(href$: Observable<string>, args: string[], options: FindListOptions): Observable<string> {
|
||||
protected buildHrefFromFindOptions(href: string, options: FindListOptions, extraArgs: string[] = []): string {
|
||||
|
||||
let args = [...extraArgs];
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||
args.push(`page=${options.currentPage - 1}`);
|
||||
args = [...args, `page=${options.currentPage - 1}`];
|
||||
}
|
||||
if (hasValue(options.elementsPerPage)) {
|
||||
args.push(`size=${options.elementsPerPage}`);
|
||||
args = [...args, `size=${options.elementsPerPage}`];
|
||||
}
|
||||
if (hasValue(options.sort)) {
|
||||
args.push(`sort=${options.sort.field},${options.sort.direction}`);
|
||||
args = [...args, `sort=${options.sort.field},${options.sort.direction}`];
|
||||
}
|
||||
if (hasValue(options.startsWith)) {
|
||||
args.push(`startsWith=${options.startsWith}`);
|
||||
args = [...args, `startsWith=${options.startsWith}`];
|
||||
}
|
||||
if (isNotEmpty(args)) {
|
||||
return href$.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString()));
|
||||
return new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
} else {
|
||||
return href$;
|
||||
return href;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,8 +185,9 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
return this.rdbService.buildSingle<T>(hrefObs);
|
||||
}
|
||||
|
||||
findByHref(href: string, options?: HttpOptions): Observable<RemoteData<T>> {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href, null, options);
|
||||
findByHref(href: string, findListOptions: FindListOptions = {}, httpOptions?: HttpOptions): Observable<RemoteData<T>> {
|
||||
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []);
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
@@ -192,6 +195,16 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
return this.rdbService.buildSingle<T>(href);
|
||||
}
|
||||
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, httpOptions?: HttpOptions): Observable<RemoteData<PaginatedList<T>>> {
|
||||
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, []);
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), requestHref, null, httpOptions);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
return this.rdbService.buildList<T>(requestHref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return object search endpoint by given search method
|
||||
*
|
||||
|
@@ -29,7 +29,7 @@ import {
|
||||
configureRequest,
|
||||
filterSuccessfulResponses,
|
||||
getRequestFromRequestHref,
|
||||
getResponseFromEntry
|
||||
getResponseFromEntry, getSucceededRemoteData
|
||||
} from '../shared/operators';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { GenericSuccessResponse, RestResponse } from '../cache/response.models';
|
||||
@@ -53,7 +53,8 @@ export class ItemDataService extends DataService<Item> {
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DSOChangeAnalyzer<Item>) {
|
||||
protected comparator: DSOChangeAnalyzer<Item>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
@@ -178,11 +178,11 @@ export class RelationshipService extends DataService<Relationship> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item its relationships in the form of an array
|
||||
* Get an item's relationships in the form of an array
|
||||
* @param item
|
||||
*/
|
||||
getItemRelationshipsArray(item: Item): Observable<Relationship[]> {
|
||||
return item.relationships.pipe(
|
||||
return this.findAllByHref(item._links.relationships.href).pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((rels: PaginatedList<Relationship>) => rels.page),
|
||||
|
@@ -61,6 +61,6 @@ export class ResourcePolicyService {
|
||||
}
|
||||
|
||||
findByHref(href: string, options?: HttpOptions): Observable<RemoteData<ResourcePolicy>> {
|
||||
return this.dataService.findByHref(href, options);
|
||||
return this.dataService.findByHref(href, {}, options);
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +1,28 @@
|
||||
import {
|
||||
catchError,
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
take
|
||||
} from 'rxjs/operators';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
|
||||
import { Meta, MetaDefinition, Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { catchError, distinctUntilKeyChanged, filter, first, map, take } from 'rxjs/operators';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { BitstreamDataService } from '../data/bitstream-data.service';
|
||||
import { BitstreamFormatDataService } from '../data/bitstream-format-data.service';
|
||||
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
import {
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteListPayload
|
||||
} from '../shared/operators';
|
||||
|
||||
@Injectable()
|
||||
export class MetadataService {
|
||||
@@ -39,6 +38,8 @@ export class MetadataService {
|
||||
private translate: TranslateService,
|
||||
private meta: Meta,
|
||||
private title: Title,
|
||||
private bitstreamDataService: BitstreamDataService,
|
||||
private bitstreamFormatDataService: BitstreamFormatDataService,
|
||||
@Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig
|
||||
) {
|
||||
// TODO: determine what open graph meta tags are needed and whether
|
||||
@@ -266,8 +267,9 @@ export class MetadataService {
|
||||
private setCitationPdfUrlTag(): void {
|
||||
if (this.currentObject.value instanceof Item) {
|
||||
const item = this.currentObject.value as Item;
|
||||
item.getFiles()
|
||||
this.bitstreamDataService.findAllByItemAndBundleName(item, 'ORIGINAL')
|
||||
.pipe(
|
||||
getFirstSucceededRemoteListPayload(),
|
||||
first((files) => isNotEmpty(files)),
|
||||
catchError((error) => {
|
||||
console.debug(error.message);
|
||||
@@ -275,17 +277,11 @@ export class MetadataService {
|
||||
}))
|
||||
.subscribe((bitstreams: Bitstream[]) => {
|
||||
for (const bitstream of bitstreams) {
|
||||
bitstream.format.pipe(
|
||||
first(),
|
||||
catchError((error: Error) => {
|
||||
console.debug(error.message);
|
||||
return []
|
||||
}),
|
||||
map((rd: RemoteData<BitstreamFormat>) => rd.payload),
|
||||
filter((format: BitstreamFormat) => hasValue(format)))
|
||||
.subscribe((format: BitstreamFormat) => {
|
||||
this.bitstreamFormatDataService.findByBitstream(bitstream).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
).subscribe((format: BitstreamFormat) => {
|
||||
if (format.mimetype === 'application/pdf') {
|
||||
this.addMetaTag('citation_pdf_url', bitstream.content);
|
||||
this.addMetaTag('citation_pdf_url', bitstream._links.content.href);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
5
src/app/core/shared/HALLink.model.ts
Normal file
5
src/app/core/shared/HALLink.model.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class HALLink {
|
||||
href: string;
|
||||
name?: string;
|
||||
templated?: boolean
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Item } from './item.model';
|
||||
import { BitstreamFormat } from './bitstream-format.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { BitstreamFormat } from './bitstream-format.model';
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { HALLink } from './HALLink.model';
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
export class Bitstream extends DSpaceObject {
|
||||
@@ -24,22 +24,24 @@ export class Bitstream extends DSpaceObject {
|
||||
bundleName: string;
|
||||
|
||||
/**
|
||||
* An array of Bitstream Format of this Bitstream
|
||||
* The Thumbnail for this Bitstream
|
||||
*/
|
||||
format: Observable<RemoteData<BitstreamFormat>>;
|
||||
thumbnail?: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bitstream
|
||||
* The Bitstream Format for this Bitstream
|
||||
*/
|
||||
parents: Observable<RemoteData<Item[]>>;
|
||||
|
||||
/**
|
||||
* The Bundle that owns this Bitstream
|
||||
*/
|
||||
owner: Observable<RemoteData<Item>>;
|
||||
format?: Observable<RemoteData<BitstreamFormat>>;
|
||||
|
||||
/**
|
||||
* The URL to retrieve this Bitstream's file
|
||||
*/
|
||||
content: string;
|
||||
|
||||
_links: {
|
||||
self: HALLink;
|
||||
bundle: HALLink;
|
||||
content: HALLink;
|
||||
format: HALLink;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,6 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs';
|
||||
import { HALLink } from './HALLink.model';
|
||||
import { ResourceType } from './resource-type';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
|
||||
export class Bundle extends DSpaceObject {
|
||||
static type = new ResourceType('bundle');
|
||||
@@ -14,24 +10,11 @@ export class Bundle extends DSpaceObject {
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The primary bitstream of this Bundle
|
||||
*/
|
||||
primaryBitstream: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bundle
|
||||
*/
|
||||
parents: Observable<RemoteData<Item[]>>;
|
||||
|
||||
/**
|
||||
* The Item that owns this Bundle
|
||||
*/
|
||||
owner: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* List of Bitstreams that are part of this Bundle
|
||||
*/
|
||||
bitstreams: Observable<RemoteData<PaginatedList<Bitstream>>>;
|
||||
|
||||
_links: {
|
||||
self: HALLink;
|
||||
primaryBitstream: HALLink;
|
||||
parents: HALLink;
|
||||
owner: HALLink;
|
||||
bitstreams: HALLink;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { HALLink } from './HALLink.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -82,4 +83,13 @@ export class Collection extends DSpaceObject {
|
||||
owner: Observable<RemoteData<Collection>>;
|
||||
|
||||
items: Observable<RemoteData<Item[]>>;
|
||||
|
||||
_links: {
|
||||
license: HALLink;
|
||||
harvester: HALLink;
|
||||
mappedItems: HALLink;
|
||||
defaultAccessConditions: HALLink;
|
||||
logo: HALLink;
|
||||
self: HALLink;
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,11 @@
|
||||
import { map, startWith, filter, switchMap } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { isEmpty } from '../../shared/empty.util';
|
||||
import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
|
||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { Collection } from './collection.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { hasValueOperator, isNotEmpty, isEmpty } from '../../shared/empty.util';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
import { Relationship } from './item-relationships/relationship.model';
|
||||
import { ResourceType } from './resource-type';
|
||||
import { getAllSucceededRemoteData, getSucceededRemoteData } from './operators';
|
||||
import { Bundle } from './bundle.model';
|
||||
import { GenericConstructor } from './generic-constructor';
|
||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||
import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
|
||||
import { HALLink } from './HALLink.model';
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
/**
|
||||
* Class representing a DSpace Item
|
||||
@@ -46,77 +38,13 @@ export class Item extends DSpaceObject {
|
||||
*/
|
||||
isWithdrawn: boolean;
|
||||
|
||||
/**
|
||||
* An array of Collections that are direct parents of this Item
|
||||
*/
|
||||
parents: Observable<RemoteData<Collection[]>>;
|
||||
|
||||
/**
|
||||
* The Collection that owns this Item
|
||||
*/
|
||||
owningCollection: Observable<RemoteData<Collection>>;
|
||||
|
||||
get owner(): Observable<RemoteData<Collection>> {
|
||||
return this.owningCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitstream bundles within this item
|
||||
*/
|
||||
bundles: Observable<RemoteData<PaginatedList<Bundle>>>;
|
||||
|
||||
relationships: Observable<RemoteData<PaginatedList<Relationship>>>;
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail of this item
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
|
||||
*/
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
// TODO: currently this just picks the first thumbnail
|
||||
// should be adjusted when we have a way to determine
|
||||
// the primary thumbnail from rest
|
||||
return this.getBitstreamsByBundleName('THUMBNAIL').pipe(
|
||||
filter((thumbnails) => isNotEmpty(thumbnails)),
|
||||
map((thumbnails) => thumbnails[0]),)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail for the given original of this item
|
||||
* @returns {Observable<Bitstream>} the primaryBitstream of the 'THUMBNAIL' bundle
|
||||
*/
|
||||
getThumbnailForOriginal(original: Bitstream): Observable<Bitstream> {
|
||||
return this.getBitstreamsByBundleName('THUMBNAIL').pipe(
|
||||
map((files) => {
|
||||
return files.find((thumbnail) => thumbnail.name.startsWith(original.name))
|
||||
}),startWith(undefined),);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all files that should be displayed on the item page of this item
|
||||
* @returns {Observable<Array<Observable<Bitstream>>>} an array of all Bitstreams in the 'ORIGINAL' bundle
|
||||
*/
|
||||
getFiles(): Observable<Bitstream[]> {
|
||||
return this.getBitstreamsByBundleName('ORIGINAL');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves bitstreams by bundle name
|
||||
* @param bundleName The name of the Bundle that should be returned
|
||||
* @returns {Observable<Bitstream[]>} the bitstreams with the given bundleName
|
||||
* TODO now that bitstreams can be paginated this should move to the server
|
||||
* see https://github.com/DSpace/dspace-angular/issues/332
|
||||
*/
|
||||
getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
|
||||
return this.bundles.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((rd: RemoteData<PaginatedList<Bundle>>) => rd.payload.page.find((bundle: Bundle) => bundle.name === bundleName)),
|
||||
hasValueOperator(),
|
||||
switchMap((bundle: Bundle) => bundle.bitstreams),
|
||||
getAllSucceededRemoteData(),
|
||||
map((rd: RemoteData<PaginatedList<Bitstream>>) => rd.payload.page),
|
||||
startWith([])
|
||||
);
|
||||
}
|
||||
_links: {
|
||||
self: HALLink;
|
||||
parents: HALLink;
|
||||
owningCollection: HALLink;
|
||||
bundles: HALLink;
|
||||
relationships: HALLink;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method that returns as which type of object this object should be rendered
|
||||
|
@@ -59,10 +59,92 @@ export const getRemoteDataPayload = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<T> =>
|
||||
source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload));
|
||||
|
||||
export const getPaginatedListPayload = () =>
|
||||
<T>(source: Observable<PaginatedList<T>>): Observable<T[]> =>
|
||||
source.pipe(map((list: PaginatedList<T>) => list.page));
|
||||
|
||||
export const getSucceededRemoteData = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
|
||||
|
||||
/**
|
||||
* Get the first successful remotely retrieved object
|
||||
*
|
||||
* You usually don't want to use this, it is a code smell.
|
||||
* Work with the RemoteData object instead, that way you can
|
||||
* handle loading and errors correctly.
|
||||
*
|
||||
* These operators were created as a first step in refactoring
|
||||
* out all the instances where this is used incorrectly.
|
||||
*/
|
||||
export const getFirstSucceededRemoteDataPayload = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<T> =>
|
||||
source.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload()
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the all successful remotely retrieved objects
|
||||
*
|
||||
* You usually don't want to use this, it is a code smell.
|
||||
* Work with the RemoteData object instead, that way you can
|
||||
* handle loading and errors correctly.
|
||||
*
|
||||
* These operators were created as a first step in refactoring
|
||||
* out all the instances where this is used incorrectly.
|
||||
*/
|
||||
export const getAllSucceededRemoteDataPayload = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<T> =>
|
||||
source.pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
getRemoteDataPayload()
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the first successful remotely retrieved paginated list
|
||||
* as an array
|
||||
*
|
||||
* You usually don't want to use this, it is a code smell.
|
||||
* Work with the RemoteData object instead, that way you can
|
||||
* handle loading and errors correctly.
|
||||
*
|
||||
* You also don't want to ignore pagination and simply use the
|
||||
* page as an array.
|
||||
*
|
||||
* These operators were created as a first step in refactoring
|
||||
* out all the instances where this is used incorrectly.
|
||||
*/
|
||||
export const getFirstSucceededRemoteListPayload = () =>
|
||||
<T>(source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> =>
|
||||
source.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
getPaginatedListPayload()
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all successful remotely retrieved paginated lists
|
||||
* as arrays
|
||||
*
|
||||
* You usually don't want to use this, it is a code smell.
|
||||
* Work with the RemoteData object instead, that way you can
|
||||
* handle loading and errors correctly.
|
||||
*
|
||||
* You also don't want to ignore pagination and simply use the
|
||||
* page as an array.
|
||||
*
|
||||
* These operators were created as a first step in refactoring
|
||||
* out all the instances where this is used incorrectly.
|
||||
*/
|
||||
export const getAllSucceededRemoteListPayload = () =>
|
||||
<T>(source: Observable<RemoteData<PaginatedList<T>>>): Observable<T[]> =>
|
||||
source.pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
getPaginatedListPayload()
|
||||
);
|
||||
|
||||
/**
|
||||
* Operator that checks if a remote data object contains a page not found error
|
||||
* When it does contain such an error, it will redirect the user to a page not found, without altering the current URL
|
||||
|
@@ -2,13 +2,13 @@
|
||||
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -2,13 +2,13 @@
|
||||
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -2,13 +2,13 @@
|
||||
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['publicationvolume.volumeNumber']"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['publicationvolume.volumeNumber']"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field class="item-page-fields" [item]="object"
|
||||
[fields]="['creativeworkseries.issn']"
|
||||
|
@@ -2,13 +2,13 @@
|
||||
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -3,13 +3,13 @@
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -2,13 +2,13 @@
|
||||
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['organization.foundingDate']"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="this.object.getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<ds-generic-item-page-field [item]="object"
|
||||
[fields]="['person.email']"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async" [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async" [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<!--<ds-generic-item-page-field [item]="object"-->
|
||||
<!--[fields]="['project.identifier.status']"-->
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<div *ngIf="logo" class="dso-logo mb-3">
|
||||
<img [src]="logo.content" class="img-fluid" [attr.alt]="alternateText ? alternateText : null" (error)="errorHandler($event)"/>
|
||||
<img [src]="logo._links.content.href" class="img-fluid" [attr.alt]="alternateText ? alternateText : null" (error)="errorHandler($event)"/>
|
||||
</div>
|
||||
|
@@ -1,9 +1,18 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import {
|
||||
getAllSucceededRemoteListPayload,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteListPayload,
|
||||
getRemoteDataPayload,
|
||||
getSucceededRemoteData
|
||||
} from '../../../../core/shared/operators';
|
||||
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
|
||||
import { fadeInOut } from '../../../animations/fade';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
@@ -64,15 +73,16 @@ export class ItemDetailPreviewComponent {
|
||||
* @param {HALEndpointService} halService
|
||||
*/
|
||||
constructor(private fileService: FileService,
|
||||
private halService: HALEndpointService) {
|
||||
private halService: HALEndpointService,
|
||||
private bitstreamDataService: BitstreamDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all instance variables
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.thumbnail$ = this.item.getThumbnail();
|
||||
this.bitstreams$ = this.item.getFiles();
|
||||
this.thumbnail$ = this.getThumbnail();
|
||||
this.bitstreams$ = this.getFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,4 +96,20 @@ export class ItemDetailPreviewComponent {
|
||||
this.fileService.downloadFile(fileUrl);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO refactor this method to return RemoteData, and the template to deal with loading and errors
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
return this.bitstreamDataService.getThumbnailFor(this.item).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
);
|
||||
}
|
||||
|
||||
// TODO refactor this method to return RemoteData, and the template to deal with loading and errors
|
||||
getFiles(): Observable<Bitstream[]> {
|
||||
return this.bitstreamDataService
|
||||
.findAllByItemAndBundleName(this.item, 'ORIGINAL', { elementsPerPage: Number.MAX_SAFE_INTEGER })
|
||||
.pipe(
|
||||
getFirstSucceededRemoteListPayload()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -30,8 +30,8 @@ export class GridThumbnailComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (hasValue(this.thumbnail) && this.thumbnail.content) {
|
||||
this.src = this.thumbnail.content;
|
||||
if (hasValue(this.thumbnail) && this.thumbnail._links.content.href) {
|
||||
this.src = this.thumbnail._links.content.href;
|
||||
} else {
|
||||
this.src = this.defaultImage
|
||||
}
|
||||
|
@@ -3,13 +3,13 @@
|
||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer" [routerLink]="['/items/' + dso.id]"
|
||||
class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="this.dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<span *ngIf="linkType == linkTypes.None" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail() | async">
|
||||
<ds-grid-thumbnail [thumbnail]="getThumbnail() | async">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</span>
|
||||
|
@@ -1,12 +1,15 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { SearchResult } from '../../search/search-result.model';
|
||||
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { hasValue } from '../../empty.util';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { TruncatableService } from '../../truncatable/truncatable.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||
import { hasValue } from '../../empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-result-grid-element',
|
||||
@@ -24,7 +27,10 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
|
||||
*/
|
||||
isCollapsed$: Observable<boolean>;
|
||||
|
||||
public constructor(protected truncatableService: TruncatableService) {
|
||||
public constructor(
|
||||
protected truncatableService: TruncatableService,
|
||||
protected bitstreamDataService: BitstreamDataService
|
||||
) {
|
||||
super();
|
||||
if (hasValue(this.object)) {
|
||||
this.isCollapsed$ = this.isCollapsed();
|
||||
@@ -63,4 +69,11 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
|
||||
private isCollapsed(): Observable<boolean> {
|
||||
return this.truncatableService.isCollapsed(this.dso.id);
|
||||
}
|
||||
|
||||
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
return this.bitstreamDataService.getThumbnailFor(this.dso as any).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -13,9 +13,22 @@ import { hasValue } from '../shared/empty.util';
|
||||
styleUrls: ['./thumbnail.component.scss'],
|
||||
templateUrl: './thumbnail.component.html'
|
||||
})
|
||||
export class ThumbnailComponent implements OnInit {
|
||||
export class ThumbnailComponent {
|
||||
|
||||
@Input() thumbnail: Bitstream;
|
||||
private _thumbnail: Bitstream;
|
||||
|
||||
get thumbnail(): Bitstream {
|
||||
return this._thumbnail;
|
||||
};
|
||||
|
||||
@Input() set thumbnail(t: Bitstream) {
|
||||
this._thumbnail = t;
|
||||
if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links.content) && this.thumbnail._links.content.href) {
|
||||
this.src = this.thumbnail._links.content.href;
|
||||
} else {
|
||||
this.src = this.defaultImage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default 'holder.js' image
|
||||
@@ -23,16 +36,8 @@ export class ThumbnailComponent implements OnInit {
|
||||
@Input() defaultImage? = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2293%22%20height%3D%22120%22%20viewBox%3D%220%200%2093%20120%22%20preserveAspectRatio%3D%22none%22%3E%3C!--%0ASource%20URL%3A%20holder.js%2F93x120%3Ftext%3DNo%20Thumbnail%0ACreated%20with%20Holder.js%202.8.2.%0ALearn%20more%20at%20http%3A%2F%2Fholderjs.com%0A(c)%202012-2015%20Ivan%20Malopinsky%20-%20http%3A%2F%2Fimsky.co%0A--%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C!%5BCDATA%5B%23holder_1543e460b05%20text%20%7B%20fill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A10pt%20%7D%20%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_1543e460b05%22%3E%3Crect%20width%3D%2293%22%20height%3D%22120%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Cg%3E%3Ctext%20x%3D%2235.6171875%22%20y%3D%2257%22%3ENo%3C%2Ftext%3E%3Ctext%20x%3D%2210.8125%22%20y%3D%2272%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E';
|
||||
|
||||
src: string;
|
||||
|
||||
errorHandler(event) {
|
||||
event.currentTarget.src = this.defaultImage;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (hasValue(this.thumbnail) && this.thumbnail.content) {
|
||||
this.src = this.thumbnail.content;
|
||||
} else {
|
||||
this.src = this.defaultImage
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ a<div class="top-item-page">
|
||||
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
<a class="btn btn-secondary"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
<a class="btn btn-secondary"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
<a class="btn btn-secondary"
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
<a class="btn btn-secondary"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"
|
||||
[defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"
|
||||
[defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="col-12 col-md-2 d-flex flex-md-column justify-content-between">
|
||||
<ds-metadata-field-wrapper>
|
||||
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"
|
||||
<ds-thumbnail [thumbnail]="getThumbnail() | async"
|
||||
[defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
<div>
|
||||
|
Reference in New Issue
Block a user