Refactored ItemDetailPreviewComponent

This commit is contained in:
Giuseppe Digilio
2019-04-05 16:33:58 +02:00
parent 156b51f37b
commit 330a70657d
10 changed files with 238 additions and 67 deletions

View File

@@ -329,7 +329,10 @@
"no-title": "No title",
"no-authors": "No Authors",
"no-date": "No Date",
"no-abstract": "No Abstract"
"no-abstract": "No Abstract",
"no-files": "No Files",
"no-uri": "No Uri",
"no-collections": "No Collections"
},
"messages": {
"title": "Messages",

View File

@@ -0,0 +1,10 @@
<ds-metadata-field-wrapper [label]="label | translate">
<ng-container *ngIf="item.hasMetadata(metadata)">
<span *ngFor="let mdValue of allMetadataValues(metadata); let last=last;">
{{mdValue}}<span *ngIf="!last" [innerHTML]="separator"></span>
</span>
</ng-container>
<ng-container *ngIf="!item.hasMetadata(metadata)">
<span class="text-muted">{{(placeholder | translate)}}</span>
</ng-container>
</ds-metadata-field-wrapper>

View File

@@ -0,0 +1,98 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ItemDetailPreviewFieldComponent } from './item-detail-preview-field.component';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { MockTranslateLoader } from '../../../../mocks/mock-translate-loader';
import { By } from '@angular/platform-browser';
let component: ItemDetailPreviewFieldComponent;
let fixture: ComponentFixture<ItemDetailPreviewFieldComponent>;
const mockItemWithAuthorAndDate: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.contributor.author': [
{
language: 'en_US',
value: 'Smith, Donald'
}
],
'dc.date.issued': [
{
language: null,
value: '2015-06-26'
}
],
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'dc.type': [
{
language: null,
value: 'Article'
}
]
}
});
describe('ItemDetailPreviewFieldComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: MockTranslateLoader
}
}),
],
declarations: [ItemDetailPreviewFieldComponent, TruncatePipe],
providers: [
{ provide: 'objectElementProvider', useValue: { mockItemWithAuthorAndDate } }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemDetailPreviewFieldComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemDetailPreviewFieldComponent);
component = fixture.componentInstance;
}));
beforeEach(() => {
component.object = { hitHighlights: {} } as any;
component.item = mockItemWithAuthorAndDate;
component.label = 'test label';
component.metadata = 'dc.title';
component.placeholder = 'No title';
fixture.detectChanges();
});
it('should display dc.title value', () => {
const span = fixture.debugElement.query(By.css('span'));
expect(span.nativeElement.innerHTML).toContain('This is just another title');
});
it('should display placeholder when metadata has no value', () => {
component.metadata = 'dc.abstract';
component.placeholder = 'No abstract';
fixture.detectChanges();
const span = fixture.debugElement.query(By.css('.text-muted'));
expect(span.nativeElement.innerHTML).toContain('No abstract');
});
});

View File

@@ -0,0 +1,55 @@
import { Component, Input } from '@angular/core';
import { Metadata } from '../../../../../core/shared/metadata.utils';
import { MyDSpaceResult } from '../../../../../+my-dspace-page/my-dspace-result.model';
import { Item } from '../../../../../core/shared/item.model';
/**
* This component show values for the given item metadata
*/
@Component({
selector: 'ds-item-detail-preview-field',
templateUrl: './item-detail-preview-field.component.html'
})
export class ItemDetailPreviewFieldComponent {
/**
* The item to display
*/
@Input() item: Item;
/**
* The mydspace result object
*/
@Input() object: MyDSpaceResult<any>;
/**
* The metadata label
*/
@Input() label: string;
/**
* The metadata to show
*/
@Input() metadata: string | string[];
/**
* The placeholder if there are no value to show
*/
@Input() placeholder: string;
/**
* The value's separator
*/
@Input() separator: string;
/**
* Gets all matching metadata string values from hitHighlights or dso metadata, preferring hitHighlights.
*
* @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see [[Metadata]].
* @returns {string[]} the matching string values or an empty array.
*/
allMetadataValues(keyOrKeys: string | string[]): string[] {
return Metadata.allValues([this.object.hitHighlights, this.item.metadata], keyOrKeys);
}
}

View File

@@ -1,10 +1,10 @@
<div class="item-page" @fadeInOut>
<div *ngIf="item" class="item-page" @fadeInOut>
<ng-container *ngIf="status">
<ds-mydspace-item-status [status]="status"></ds-mydspace-item-status>
</ng-container>
<div *ngIf="item">
<h2 class="item-page-title-field">
<ds-metadata-values *ngIf="item.hasMetadata('dc.title')" [mdValues]="item?.allMetadata(fields)"></ds-metadata-values>
<ds-metadata-values *ngIf="item.hasMetadata('dc.title')" [mdValues]="item?.allMetadata('dc.title')"></ds-metadata-values>
<span class="text-muted" *ngIf="!item.hasMetadata('dc.title')">{{('mydspace.results.no-title' | translate)}}</span>
</h2>
<div class="row mb-1">
@@ -12,38 +12,46 @@
<ds-metadata-field-wrapper>
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
</ds-metadata-field-wrapper>
<ds-metadata-field-wrapper [label]="'item.page.date' | translate">
<ng-container *ngIf="item.hasMetadata('dc.date.issued')">
<span *ngFor="let mdValue of allMetadataValues('dc.date.issued'); let last=last;">
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
</span>
</ng-container>
<ng-container *ngIf="!item.hasMetadata('dc.date.issued')">
<span class="text-muted">{{('mydspace.results.no-date' | translate)}}</span>
</ng-container>
</ds-metadata-field-wrapper>
<ds-metadata-field-wrapper [label]="'item.page.author' | translate">
<ng-container *ngIf="item.hasMetadata(['dc.contributor', 'dc.creator', 'dc.contributor.*']);">
<span *ngFor="let mdValue of allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
</span>
</ng-container>
<ng-container *ngIf="!item.hasMetadata(['dc.contributor', 'dc.creator', 'dc.contributor.*']);">
<span class="text-muted">{{('mydspace.results.no-authors' | translate)}}</span>
</ng-container>
</ds-metadata-field-wrapper>
<ng-container *ngVar="(bitstreams$ | async) as bitstreams">
<ds-metadata-field-wrapper [label]="('item.page.files' | translate)">
<div *ngIf="bitstreams?.length > 0" class="file-section">
<a *ngFor="let file of bitstreams; let last=last;" [href]="file?.content" target="_blank" [download]="file?.name">
<span>{{file?.name}}</span>
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
<span *ngIf="!last" innerHTML="{{separator}}"></span>
</a>
</div>
<ng-container *ngIf="bitstreams?.length === 0">
<span class="text-muted">{{('mydspace.results.no-files' | translate)}}</span>
</ng-container>
</ds-metadata-field-wrapper>
</ng-container>
<ds-item-detail-preview-field [item]="item"
[object]="object"
[label]="('item.page.date' | translate)"
[metadata]="'dc.date.issued'"
[separator]="separator"
[placeholder]="('mydspace.results.no-date' | translate)"></ds-item-detail-preview-field>
<ds-item-detail-preview-field [item]="item"
[object]="object"
[label]="('item.page.author' | translate)"
[metadata]="['dc.contributor', 'dc.creator', 'dc.contributor.*']"
[separator]="separator"
[placeholder]="('mydspace.results.no-authors' | translate)"></ds-item-detail-preview-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-metadata-field-wrapper [label]="'item.page.abstract' | translate">
<ng-container *ngIf="item.hasMetadata('dc.description.abstract');">
<span *ngFor="let mdValue of allMetadataValues('dc.description.abstract'); let last=last;">
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
</span>
</ng-container>
<ng-container *ngIf="!item.hasMetadata('dc.description.abstract');">
<span class="text-muted">{{('mydspace.results.no-abstract' | translate)}}</span>
</ng-container>
</ds-metadata-field-wrapper>
<ds-item-detail-preview-field [item]="item"
[object]="object"
[label]="('item.page.abstract' | translate)"
[metadata]="'dc.description.abstract'"
[separator]="separator"
[placeholder]="('mydspace.results.no-abstract' | translate)"></ds-item-detail-preview-field>
<ds-item-detail-preview-field [item]="item"
[object]="object"
[label]="('item.page.uri' | translate)"
[metadata]="'dc.identifier.uri'"
[separator]="separator"
[placeholder]="('mydspace.results.no-uri' | translate)"></ds-item-detail-preview-field>
<div>
<ng-content></ng-content>
</div>

View File

@@ -1,5 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
@@ -8,13 +9,15 @@ import { TruncatePipe } from '../../../utils/truncate.pipe';
import { Item } from '../../../../core/shared/item.model';
import { ItemDetailPreviewComponent } from './item-detail-preview.component';
import { MockTranslateLoader } from '../../../mocks/mock-translate-loader';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ItemDetailPreviewFieldComponent } from './item-detail-preview-field/item-detail-preview-field.component';
import { FileSizePipe } from '../../../utils/file-size-pipe';
import { VarDirective } from '../../../utils/var.directive';
let component: ItemDetailPreviewComponent;
let fixture: ComponentFixture<ItemDetailPreviewComponent>;
const mockItemWithAuthorAndDate: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf([]),
metadata: {
'dc.contributor.author': [
{
@@ -27,12 +30,7 @@ const mockItemWithAuthorAndDate: Item = Object.assign(new Item(), {
language: null,
value: '2015-06-26'
}
]
}
});
const mockItemWithoutAuthorAndDate: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
],
'dc.title': [
{
language: 'en_US',
@@ -60,12 +58,7 @@ describe('ItemDetailPreviewComponent', () => {
}
}),
],
declarations: [ItemDetailPreviewComponent, TruncatePipe],
providers: [
{ provide: 'objectElementProvider', useValue: { mockItemWithAuthorAndDate } }
],
declarations: [ItemDetailPreviewComponent, ItemDetailPreviewFieldComponent, TruncatePipe, FileSizePipe, VarDirective],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemDetailPreviewComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
@@ -75,16 +68,16 @@ describe('ItemDetailPreviewComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemDetailPreviewComponent);
component = fixture.componentInstance;
component.object = { hitHighlights: {} } as any;
component.item = mockItem;
component.separator = ', ';
spyOn(component.item, 'getFiles').and.returnValue(mockItem.bitstreams);
fixture.detectChanges();
}));
beforeEach(() => {
component.object = { hitHighlights: {} };
component.item = mockItemWithAuthorAndDate;
fixture.detectChanges();
});
it('should init thumbnail on init', () => {
it('should init thumbnail and bitstreams on init', () => {
expect(component.thumbnail$).toBeDefined();
expect(component.bitstreams$).toBeDefined();
});
});

View File

@@ -6,7 +6,7 @@ import { Item } from '../../../../core/shared/item.model';
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';
import { Metadata } from '../../../../core/shared/metadata.utils';
import { MyDSpaceResult } from '../../../../+my-dspace-page/my-dspace-result.model';
/**
* This component show metadata for the given item object in the detail view.
@@ -27,7 +27,7 @@ export class ItemDetailPreviewComponent {
/**
* The mydspace result object
*/
@Input() object: any;
@Input() object: MyDSpaceResult<any>;
/**
* Represent item's status
@@ -42,23 +42,24 @@ export class ItemDetailPreviewComponent {
/**
* The item's thumbnail
*/
public thumbnail$: Observable<Bitstream>;
public bitstreams$: Observable<Bitstream[]>;
/**
* Gets all matching metadata string values from hitHighlights or dso metadata, preferring hitHighlights.
*
* @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see [[Metadata]].
* @returns {string[]} the matching string values or an empty array.
* The value's separator
*/
allMetadataValues(keyOrKeys: string | string[]): string[] {
return Metadata.allValues([this.object.hitHighlights, this.item.metadata], keyOrKeys);
}
public separator = ', ';
/**
* The item's thumbnail
*/
public thumbnail$: Observable<Bitstream>;
/**
* Initialize all instance variables
*/
ngOnInit() {
this.thumbnail$ = this.item.getThumbnail();
this.bitstreams$ = this.item.getFiles();
}
}

View File

@@ -78,7 +78,7 @@ describe('ItemListPreviewComponent', () => {
}));
beforeEach(() => {
component.object = { hitHighlights: {} };
component.object = { hitHighlights: {} } as any;
});
describe('When the item has an author', () => {

View File

@@ -4,6 +4,7 @@ import { Item } from '../../../../core/shared/item.model';
import { fadeInOut } from '../../../animations/fade';
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { Metadata } from '../../../../core/shared/metadata.utils';
import { MyDSpaceResult } from '../../../../+my-dspace-page/my-dspace-result.model';
/**
* This component show metadata for the given item object in the list view.
@@ -24,7 +25,7 @@ export class ItemListPreviewComponent {
/**
* The mydspace result object
*/
@Input() object: any;
@Input() object: MyDSpaceResult<any>;
/**
* Represent item's status

View File

@@ -130,6 +130,7 @@ import { MetadataValuesComponent } from '../+item-page/field-components/metadata
import { RoleDirective } from './roles/role.directive';
import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component';
import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component';
import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -219,6 +220,7 @@ const COMPONENTS = [
MyDSpaceItemStatusComponent,
ItemSubmitterComponent,
ItemDetailPreviewComponent,
ItemDetailPreviewFieldComponent,
ClaimedTaskActionsComponent,
ClaimedTaskActionsApproveComponent,
ClaimedTaskActionsRejectComponent,