mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 15:03:07 +00:00
add the ability to mix text based metadata fields, authority controlled fields and typed items in the same section
This commit is contained in:
@@ -207,7 +207,7 @@
|
||||
"f.dateIssued.max": "End date",
|
||||
"f.subject": "Subject",
|
||||
"f.has_content_in_original_bundle": "Has files",
|
||||
"f.entityType": "Item Type"
|
||||
"f.entityType": "Entity Type"
|
||||
},
|
||||
"filter": {
|
||||
"show-more": "Show more",
|
||||
@@ -237,8 +237,8 @@
|
||||
"head": "Has files"
|
||||
},
|
||||
"entityType": {
|
||||
"placeholder": "Item Type",
|
||||
"head": "Item Type"
|
||||
"placeholder": "Entity Type",
|
||||
"head": "Entity Type"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
|
||||
import { MetadataValuesComponent } from './metadata-values.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Metadatum } from '../../../core/shared/metadatum.model';
|
||||
|
||||
let comp: MetadataValuesComponent;
|
||||
let fixture: ComponentFixture<MetadataValuesComponent>;
|
||||
@@ -23,7 +24,7 @@ const mockMetadata = [
|
||||
key: 'journal.identifier.description',
|
||||
language: 'en_US',
|
||||
value: 'desc'
|
||||
}];
|
||||
}] as Metadatum[];
|
||||
const mockSeperator = '<br/>';
|
||||
const mockLabel = 'fake.message';
|
||||
|
||||
|
@@ -29,6 +29,7 @@ import { JournalComponent } from './simple/item-types/journal/journal.component'
|
||||
import { JournalVolumeComponent } from './simple/item-types/journal-volume/journal-volume.component';
|
||||
import { JournalIssueComponent } from './simple/item-types/journal-issue/journal-issue.component';
|
||||
import { ItemComponent } from './simple/item-types/shared/item.component';
|
||||
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -61,7 +62,8 @@ import { ItemComponent } from './simple/item-types/shared/item.component';
|
||||
GenericItemPageFieldComponent,
|
||||
JournalComponent,
|
||||
JournalIssueComponent,
|
||||
JournalVolumeComponent
|
||||
JournalVolumeComponent,
|
||||
MetadataRepresentationListComponent
|
||||
],
|
||||
entryComponents: [
|
||||
PublicationComponent,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="itemRD?.payload as item">
|
||||
<ds-item-type-switcher [object]="item" [viewMode]="ElementViewMode.Full"></ds-item-type-switcher>
|
||||
<ds-item-type-switcher [object]="item" [viewMode]="viewMode"></ds-item-type-switcher>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||
|
@@ -15,6 +15,8 @@ import { fadeInOut } from '../../shared/animations/fade';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import * as viewMode from '../../shared/view-mode';
|
||||
|
||||
export const VIEW_MODE_FULL = 'full';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
@@ -46,9 +48,8 @@ export class ItemPageComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The view-mode we're currently on
|
||||
* @type {ElementViewMode}
|
||||
*/
|
||||
ElementViewMode = viewMode.ElementViewMode;
|
||||
viewMode = VIEW_MODE_FULL;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
|
@@ -4,11 +4,11 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('JournalIssue', ElementViewMode.Full)
|
||||
@rendersItemType('JournalIssue', VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-journal-issue',
|
||||
styleUrls: ['./journal-issue.component.scss'],
|
||||
|
@@ -4,11 +4,11 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('JournalVolume', ElementViewMode.Full)
|
||||
@rendersItemType('JournalVolume', VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-journal-volume',
|
||||
styleUrls: ['./journal-volume.component.scss'],
|
||||
|
@@ -4,11 +4,11 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('Journal', ElementViewMode.Full)
|
||||
@rendersItemType('Journal', VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-journal',
|
||||
styleUrls: ['./journal.component.scss'],
|
||||
|
@@ -3,12 +3,12 @@ import { Observable } from 'rxjs';
|
||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('OrgUnit', ElementViewMode.Full)
|
||||
@rendersItemType('OrgUnit', VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-orgunit',
|
||||
styleUrls: ['./orgunit.component.scss'],
|
||||
|
@@ -4,12 +4,12 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('Person', ElementViewMode.Full)
|
||||
@rendersItemType('Person', VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-person',
|
||||
styleUrls: ['./person.component.scss'],
|
||||
|
@@ -3,12 +3,12 @@ import { Observable } from 'rxjs';
|
||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('Project', ElementViewMode.Full)
|
||||
@rendersItemType('Project', VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-project',
|
||||
styleUrls: ['./project.component.scss'],
|
||||
|
@@ -9,10 +9,10 @@
|
||||
<ds-item-page-author-field *ngIf="!(authors$ | async)" [item]="item"></ds-item-page-author-field>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<ds-related-items
|
||||
[items]="authors$ | async"
|
||||
[label]="'relationships.isAuthorOf' | translate">
|
||||
</ds-related-items>
|
||||
<ds-metadata-representation-list
|
||||
[label]="'relationships.isAuthorOf' | translate"
|
||||
[representations]="authors$ | async">
|
||||
</ds-metadata-representation-list>
|
||||
<ds-related-items
|
||||
[items]="projects$ | async"
|
||||
[label]="'relationships.isProjectOf' | translate">
|
||||
|
@@ -7,11 +7,12 @@ import {
|
||||
rendersItemType
|
||||
} from '../../../../shared/items/item-type-decorator';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { ElementViewMode } from '../../../../shared/view-mode';
|
||||
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
|
||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { VIEW_MODE_FULL } from '../../item-page.component';
|
||||
|
||||
@rendersItemType('Publication', ElementViewMode.Full)
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, ElementViewMode.Full)
|
||||
@rendersItemType('Publication', VIEW_MODE_FULL)
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_FULL)
|
||||
@Component({
|
||||
selector: 'ds-publication',
|
||||
styleUrls: ['./publication.component.scss'],
|
||||
@@ -22,7 +23,7 @@ export class PublicationComponent extends ItemComponent implements OnInit {
|
||||
/**
|
||||
* The authors related to this publication
|
||||
*/
|
||||
authors$: Observable<Item[]>;
|
||||
authors$: Observable<MetadataRepresentation[]>;
|
||||
|
||||
/**
|
||||
* The projects related to this publication
|
||||
@@ -51,10 +52,7 @@ export class PublicationComponent extends ItemComponent implements OnInit {
|
||||
|
||||
if (this.resolvedRelsAndTypes$) {
|
||||
|
||||
this.authors$ = this.resolvedRelsAndTypes$.pipe(
|
||||
filterRelationsByTypeLabel('isAuthorOfPublication'),
|
||||
relationsToItems(this.item.id, this.ids)
|
||||
);
|
||||
this.authors$ = this.buildRepresentations('Person', 'dc.contributor.author', this.ids);
|
||||
|
||||
this.projects$ = this.resolvedRelsAndTypes$.pipe(
|
||||
filterRelationsByTypeLabel('isProjectOfPublication'),
|
||||
|
@@ -16,8 +16,18 @@ import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||
import { compareArraysUsing, compareArraysUsingIds } from './item.component';
|
||||
import { compareArraysUsing, compareArraysUsingIds, ItemComponent } from './item.component';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ItemPageComponent } from '../../item-page.component';
|
||||
import { VarDirective } from '../../../../shared/utils/var.directive';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MetadataService } from '../../../../core/metadata/metadata.service';
|
||||
import { of } from 'rxjs/internal/observable/of';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||
|
||||
/**
|
||||
* Create a generic test for an item-page-fields component using a mockItem and the type of component
|
||||
@@ -306,4 +316,116 @@ describe('ItemComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when calling buildRepresentations', () => {
|
||||
let comp: ItemComponent;
|
||||
let fixture: ComponentFixture<ItemComponent>;
|
||||
|
||||
const metadataField = 'dc.contributor.author';
|
||||
const mockItem = Object.assign(new Item(), {
|
||||
id: '1',
|
||||
uuid: '1',
|
||||
metadata: [
|
||||
{
|
||||
key: metadataField,
|
||||
value: 'Second value',
|
||||
place: 1
|
||||
},
|
||||
{
|
||||
key: metadataField,
|
||||
value: 'Third value',
|
||||
place: 2,
|
||||
authority: 'virtual::123'
|
||||
},
|
||||
{
|
||||
key: metadataField,
|
||||
value: 'First value',
|
||||
place: 0
|
||||
},
|
||||
{
|
||||
key: metadataField,
|
||||
value: 'Fourth value',
|
||||
place: 3,
|
||||
authority: '123'
|
||||
}
|
||||
],
|
||||
relationships: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [
|
||||
Object.assign(new Relationship(), {
|
||||
uuid: '123',
|
||||
id: '123',
|
||||
leftId: '1',
|
||||
rightId: '2',
|
||||
relationshipType: observableOf(new RemoteData(false, false, true, null, new RelationshipType()))
|
||||
})
|
||||
])))
|
||||
});
|
||||
const relatedItem = Object.assign(new Item(), {
|
||||
id: '2',
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
value: 'related item'
|
||||
}
|
||||
]
|
||||
});
|
||||
const mockItemDataService = {
|
||||
findById: (id) => {
|
||||
if (id === relatedItem.id) {
|
||||
return observableOf(new RemoteData(false, false, true, null, relatedItem))
|
||||
}
|
||||
}
|
||||
} as ItemDataService;
|
||||
|
||||
let representations: Observable<MetadataRepresentation[]>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: MockTranslateLoader
|
||||
}
|
||||
}), BrowserAnimationsModule],
|
||||
declarations: [ItemComponent, VarDirective],
|
||||
providers: [
|
||||
{provide: ITEM, useValue: mockItem}
|
||||
],
|
||||
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(ItemComponent, {
|
||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(ItemComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
representations = comp.buildRepresentations('bogus', metadataField, mockItemDataService);
|
||||
}));
|
||||
|
||||
it('should contain exactly 4 metadata-representations', () => {
|
||||
representations.subscribe((reps: MetadataRepresentation[]) => {
|
||||
expect(reps.length).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have all the representations in the correct order', () => {
|
||||
representations.subscribe((reps: MetadataRepresentation[]) => {
|
||||
expect(reps[0].getPrimaryValue()).toEqual('First value');
|
||||
expect(reps[1].getPrimaryValue()).toEqual('Second value');
|
||||
expect(reps[2].getPrimaryValue()).toEqual('related item');
|
||||
expect(reps[3].getPrimaryValue()).toEqual('Fourth value');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have created the correct MetadatumRepresentation and ItemMetadataRepresentation objects for the correct Metadata', () => {
|
||||
representations.subscribe((reps: MetadataRepresentation[]) => {
|
||||
expect(reps[0] instanceof MetadatumRepresentation).toEqual(true);
|
||||
expect(reps[1] instanceof MetadatumRepresentation).toEqual(true);
|
||||
expect(reps[2] instanceof ItemMetadataRepresentation).toEqual(true);
|
||||
expect(reps[3] instanceof MetadatumRepresentation).toEqual(true);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
|
@@ -7,9 +7,14 @@ import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { hasNoValue, hasValue } from '../../../../shared/empty.util';
|
||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||
import { Metadatum } from '../../../../core/shared/metadatum.model';
|
||||
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||
import { of } from 'rxjs/internal/observable/of';
|
||||
|
||||
/**
|
||||
* Operator for comparing arrays using a mapping function
|
||||
@@ -82,6 +87,41 @@ export const relationsToItems = (thisId: string, ids: ItemDataService) =>
|
||||
distinctUntilChanged(compareArraysUsingIds()),
|
||||
);
|
||||
|
||||
/**
|
||||
* Operator for turning a list of relationships into a list of metadatarepresentations given the original metadata
|
||||
* @param thisId The id of the parent item
|
||||
* @param itemType The type of relation this list resembles (for creating representations)
|
||||
* @param metadata The list of original Metadatum objects
|
||||
* @param ids The ItemDataService to use for fetching Items from the Rest API
|
||||
*/
|
||||
export const relationsToRepresentations = (thisId: string, itemType: string, metadata: Metadatum[], ids: ItemDataService) =>
|
||||
(source: Observable<Relationship[]>): Observable<MetadataRepresentation[]> =>
|
||||
source.pipe(
|
||||
flatMap((rels: Relationship[]) =>
|
||||
observableZip(
|
||||
...metadata.map((metadatum: Metadatum) => {
|
||||
const prefix = 'virtual::';
|
||||
if (hasValue(metadatum.authority) && metadatum.authority.startsWith(prefix)) {
|
||||
const matchingRels = rels.filter((rel: Relationship) => ('' + rel.id) === metadatum.authority.substring(metadatum.authority.indexOf(prefix) + prefix.length));
|
||||
if (matchingRels.length > 0) {
|
||||
const matchingRel = matchingRels[0];
|
||||
let queryId = matchingRel.leftId;
|
||||
if (matchingRel.leftId === thisId) {
|
||||
queryId = matchingRel.rightId;
|
||||
}
|
||||
return ids.findById(queryId).pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((d: RemoteData<Item>) => Object.assign(new ItemMetadataRepresentation(itemType), d.payload))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return of(Object.assign(new MetadatumRepresentation(itemType), metadatum));
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item',
|
||||
template: ''
|
||||
@@ -93,7 +133,7 @@ export class ItemComponent implements OnInit {
|
||||
/**
|
||||
* Resolved relationships and types together in one observable
|
||||
*/
|
||||
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>
|
||||
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>;
|
||||
|
||||
constructor(
|
||||
@Inject(ITEM) public item: Item
|
||||
@@ -125,4 +165,25 @@ export class ItemComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of MetadataRepresentations for the current item. This combines all metadata and relationships of a
|
||||
* certain type.
|
||||
* @param itemType The type of item we're building representations of. Used for matching templates.
|
||||
* @param metadataField The metadata field that resembles the item type.
|
||||
* @param itemDataService ItemDataService to turn relations into items.
|
||||
*/
|
||||
buildRepresentations(itemType: string, metadataField: string, itemDataService: ItemDataService): Observable<MetadataRepresentation[]> {
|
||||
const metadata = this.item.findMetadataSortedByPlace(metadataField);
|
||||
const relsCurrentPage$ = this.item.relationships.pipe(
|
||||
getSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((pl: PaginatedList<Relationship>) => pl.page),
|
||||
distinctUntilChanged(compareArraysUsingIds())
|
||||
);
|
||||
|
||||
return relsCurrentPage$.pipe(
|
||||
relationsToRepresentations(this.item.id, itemType, metadata, itemDataService)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,5 @@
|
||||
<ds-metadata-field-wrapper *ngIf="representations && representations.length > 0" [label]="label">
|
||||
<ds-item-type-switcher *ngFor="let rep of representations"
|
||||
[object]="rep" [viewMode]="viewMode">
|
||||
</ds-item-type-switcher>
|
||||
</ds-metadata-field-wrapper>
|
@@ -0,0 +1,40 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MetadataRepresentationListComponent } from './metadata-representation-list.component';
|
||||
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||
|
||||
const itemType = 'type';
|
||||
const metadataRepresentation1 = new MetadatumRepresentation(itemType);
|
||||
const metadataRepresentation2 = new ItemMetadataRepresentation(itemType);
|
||||
const representations = [metadataRepresentation1, metadataRepresentation2];
|
||||
|
||||
describe('MetadataRepresentationListComponent', () => {
|
||||
let comp: MetadataRepresentationListComponent;
|
||||
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
declarations: [MetadataRepresentationListComponent],
|
||||
providers: [],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(MetadataRepresentationListComponent, {
|
||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(MetadataRepresentationListComponent);
|
||||
comp = fixture.componentInstance;
|
||||
comp.representations = representations;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it(`should load ${representations.length} item-type-switcher components`, () => {
|
||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
|
||||
expect(fields.length).toBe(representations.length);
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,31 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import * as viewMode from '../../../shared/view-mode';
|
||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
|
||||
export const VIEW_MODE_METADATA = 'metadata';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-representation-list',
|
||||
templateUrl: './metadata-representation-list.component.html'
|
||||
})
|
||||
/**
|
||||
* This component is used for displaying metadata
|
||||
* It expects a list of MetadataRepresentation objects and a label to put on top of the list
|
||||
*/
|
||||
export class MetadataRepresentationListComponent {
|
||||
/**
|
||||
* A list of metadata-representations to display
|
||||
*/
|
||||
@Input() representations: MetadataRepresentation[];
|
||||
|
||||
/**
|
||||
* An i18n label to use as a title for the list
|
||||
*/
|
||||
@Input() label: string;
|
||||
|
||||
/**
|
||||
* The view-mode we're currently on
|
||||
* @type {ElementViewMode}
|
||||
*/
|
||||
viewMode = VIEW_MODE_METADATA;
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import * as viewMode from '../../../shared/view-mode';
|
||||
|
||||
export const VIEW_MODE_ELEMENT = 'element';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-related-items',
|
||||
@@ -26,5 +27,5 @@ export class RelatedItemsComponent {
|
||||
* The view-mode we're currently on
|
||||
* @type {ElementViewMode}
|
||||
*/
|
||||
ElementViewMode = viewMode.ElementViewMode
|
||||
viewMode = VIEW_MODE_ELEMENT;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<ds-metadata-field-wrapper *ngIf="items && items.length > 0" [label]="label">
|
||||
<ds-item-type-switcher *ngFor="let item of items"
|
||||
[object]="item" [viewMode]="ElementViewMode.SetElement">
|
||||
[object]="item" [viewMode]="viewMode">
|
||||
</ds-item-type-switcher>
|
||||
</ds-metadata-field-wrapper>
|
||||
|
@@ -33,6 +33,7 @@ import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
|
||||
import { BrowseService } from '../browse/browse.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { EmptyError } from 'rxjs/internal-compatibility';
|
||||
import { Metadatum } from '../shared/metadatum.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
@Component({
|
||||
@@ -223,7 +224,7 @@ describe('MetadataService', () => {
|
||||
key: 'dc.publisher',
|
||||
language: 'en_US',
|
||||
value: 'Mock Publisher'
|
||||
});
|
||||
} as Metadatum);
|
||||
return publishedMockItem;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Metadatum } from './metadatum.model'
|
||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { hasNoValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { ResourceType } from './resource-type';
|
||||
@@ -91,4 +91,23 @@ export class DSpaceObject implements CacheableObject, ListableObject {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find metadata on a specific field and order all of them using their "place" property.
|
||||
* @param key
|
||||
*/
|
||||
findMetadataSortedByPlace(key: string): Metadatum[] {
|
||||
return this.filterMetadata([key]).sort((a: Metadatum, b: Metadatum) => {
|
||||
if (hasNoValue(a.place) && hasNoValue(b.place)) {
|
||||
return 0;
|
||||
}
|
||||
if (hasNoValue(a.place)) {
|
||||
return -1;
|
||||
}
|
||||
if (hasNoValue(b.place)) {
|
||||
return 1;
|
||||
}
|
||||
return a.place - b.place;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,36 @@
|
||||
import { MetadataRepresentationType } from '../metadata-representation.model';
|
||||
import { ItemMetadataRepresentation, ItemTypeToPrimaryValue } from './item-metadata-representation.model';
|
||||
import { Item } from '../../item.model';
|
||||
import { Metadatum } from '../../metadatum.model';
|
||||
|
||||
describe('ItemMetadataRepresentation', () => {
|
||||
const valuePrefix = 'Test value for ';
|
||||
const item = new Item();
|
||||
let itemMetadataRepresentation: ItemMetadataRepresentation;
|
||||
item.metadata = Object.keys(ItemTypeToPrimaryValue).map((key: string) => {
|
||||
return Object.assign(new Metadatum(), {
|
||||
key: ItemTypeToPrimaryValue[key],
|
||||
value: `${valuePrefix}${ItemTypeToPrimaryValue[key]}`
|
||||
});
|
||||
});
|
||||
|
||||
for (const itemType of Object.keys(ItemTypeToPrimaryValue)) {
|
||||
describe(`when creating an ItemMetadataRepresentation with item-type "${itemType}"`, () => {
|
||||
beforeEach(() => {
|
||||
itemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(itemType), item);
|
||||
});
|
||||
|
||||
it('should have a representation type of item', () => {
|
||||
expect(itemMetadataRepresentation.representationType).toEqual(MetadataRepresentationType.Item);
|
||||
});
|
||||
|
||||
it('should return the correct value when calling getPrimaryValue', () => {
|
||||
expect(itemMetadataRepresentation.getPrimaryValue()).toEqual(`${valuePrefix}${ItemTypeToPrimaryValue[itemType]}`);
|
||||
});
|
||||
|
||||
it('should return the correct item type', () => {
|
||||
expect(itemMetadataRepresentation.itemType).toEqual(itemType);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@@ -0,0 +1,48 @@
|
||||
import { Item } from '../../item.model';
|
||||
import { MetadataRepresentation, MetadataRepresentationType } from '../metadata-representation.model';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* An object to convert item types into the metadata field it should render for the item's primary value
|
||||
*/
|
||||
export const ItemTypeToPrimaryValue = {
|
||||
Default: 'dc.title',
|
||||
Person: 'dc.contributor.author'
|
||||
};
|
||||
|
||||
/**
|
||||
* This class defines the way the item it extends should be represented as metadata
|
||||
*/
|
||||
export class ItemMetadataRepresentation extends Item implements MetadataRepresentation {
|
||||
|
||||
/**
|
||||
* The type of item this item can be represented as
|
||||
*/
|
||||
itemType: string;
|
||||
|
||||
constructor(itemType: string) {
|
||||
super();
|
||||
this.itemType = itemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the way this item should be rendered as in a list
|
||||
*/
|
||||
get representationType(): MetadataRepresentationType {
|
||||
return MetadataRepresentationType.Item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to display, depending on the itemType
|
||||
*/
|
||||
getPrimaryValue(): string {
|
||||
let metadata;
|
||||
if (hasValue(ItemTypeToPrimaryValue[this.itemType])) {
|
||||
metadata = ItemTypeToPrimaryValue[this.itemType];
|
||||
} else {
|
||||
metadata = ItemTypeToPrimaryValue.Default;
|
||||
}
|
||||
return this.findMetadata(metadata);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* An Enum defining the representation type of metadata
|
||||
*/
|
||||
export enum MetadataRepresentationType {
|
||||
None = 'none',
|
||||
Item = 'item',
|
||||
AuthorityControlled = 'authority_controlled',
|
||||
PlainText = 'plain_text'
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface containing information about how we should represent certain metadata
|
||||
*/
|
||||
export interface MetadataRepresentation {
|
||||
/**
|
||||
* The type of item this metadata is representing
|
||||
* e.g. 'Person'
|
||||
* This can be used for template matching
|
||||
*/
|
||||
itemType: string;
|
||||
|
||||
/**
|
||||
* How we should render the metadata in a list
|
||||
*/
|
||||
representationType: MetadataRepresentationType,
|
||||
|
||||
/**
|
||||
* Fetches the primary value to be displayed
|
||||
*/
|
||||
getPrimaryValue(): string
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
import { Metadatum } from '../../metadatum.model';
|
||||
import { MetadatumRepresentation } from './metadatum-representation.model';
|
||||
import { MetadataRepresentationType } from '../metadata-representation.model';
|
||||
|
||||
describe('MetadatumRepresentation', () => {
|
||||
const itemType = 'Person';
|
||||
const normalMetadatum = Object.assign(new Metadatum(), {
|
||||
key: 'dc.contributor.author',
|
||||
value: 'Test Author'
|
||||
});
|
||||
const authorityMetadatum = Object.assign(new Metadatum(), {
|
||||
key: 'dc.contributor.author',
|
||||
value: 'Test Authority Author',
|
||||
authority: '1234'
|
||||
});
|
||||
|
||||
let metadatumRepresentation: MetadatumRepresentation;
|
||||
|
||||
describe('when creating a MetadatumRepresentation based on a standard Metadatum object', () => {
|
||||
beforeEach(() => {
|
||||
metadatumRepresentation = Object.assign(new MetadatumRepresentation(itemType), normalMetadatum);
|
||||
});
|
||||
|
||||
it('should have a representation type of plain text', () => {
|
||||
expect(metadatumRepresentation.representationType).toEqual(MetadataRepresentationType.PlainText);
|
||||
});
|
||||
|
||||
it('should return the correct value when calling getPrimaryValue', () => {
|
||||
expect(metadatumRepresentation.getPrimaryValue()).toEqual(normalMetadatum.value);
|
||||
});
|
||||
|
||||
it('should return the correct item type', () => {
|
||||
expect(metadatumRepresentation.itemType).toEqual(itemType);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when creating a MetadatumRepresentation based on an authority controlled Metadatum object', () => {
|
||||
beforeEach(() => {
|
||||
metadatumRepresentation = Object.assign(new MetadatumRepresentation(itemType), authorityMetadatum);
|
||||
});
|
||||
|
||||
it('should have a representation type of plain text', () => {
|
||||
expect(metadatumRepresentation.representationType).toEqual(MetadataRepresentationType.AuthorityControlled);
|
||||
});
|
||||
|
||||
it('should return the correct value when calling getPrimaryValue', () => {
|
||||
expect(metadatumRepresentation.getPrimaryValue()).toEqual(authorityMetadatum.value);
|
||||
});
|
||||
|
||||
it('should return the correct item type', () => {
|
||||
expect(metadatumRepresentation.itemType).toEqual(itemType);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,38 @@
|
||||
import { Metadatum } from '../../metadatum.model';
|
||||
import { MetadataRepresentation, MetadataRepresentationType } from '../metadata-representation.model';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This class defines the way the metadatum it extends should be represented
|
||||
*/
|
||||
export class MetadatumRepresentation extends Metadatum implements MetadataRepresentation {
|
||||
|
||||
/**
|
||||
* The type of item this metadatum can be represented as
|
||||
*/
|
||||
itemType: string;
|
||||
|
||||
constructor(itemType: string) {
|
||||
super();
|
||||
this.itemType = itemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the way this metadatum should be rendered as in a list
|
||||
*/
|
||||
get representationType(): MetadataRepresentationType {
|
||||
if (hasValue(this.authority)) {
|
||||
return MetadataRepresentationType.AuthorityControlled;
|
||||
} else {
|
||||
return MetadataRepresentationType.PlainText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to display
|
||||
*/
|
||||
getPrimaryValue(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
@@ -20,4 +20,17 @@ export class Metadatum {
|
||||
@autoserialize
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* The place of this Metadatum within his list of metadata
|
||||
* This is used to render metadata in a specific custom order
|
||||
*/
|
||||
@autoserialize
|
||||
place: number;
|
||||
|
||||
/**
|
||||
* The authority key used for authority-controlled metadata
|
||||
*/
|
||||
@autoserialize
|
||||
authority: string;
|
||||
|
||||
}
|
||||
|
@@ -1,36 +1,60 @@
|
||||
import { hasNoValue, hasValue } from '../empty.util';
|
||||
import { ElementViewMode } from '../view-mode';
|
||||
import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { VIEW_MODE_ELEMENT } from '../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
export const DEFAULT_ITEM_TYPE = 'Default';
|
||||
export const DEFAULT_VIEW_MODE = VIEW_MODE_ELEMENT;
|
||||
export const NO_REPRESENTATION_TYPE = MetadataRepresentationType.None;
|
||||
export const DEFAULT_REPRESENTATION_TYPE = MetadataRepresentationType.PlainText;
|
||||
|
||||
const map = new Map();
|
||||
|
||||
/**
|
||||
* Decorator used for rendering simple item pages by type and viewMode
|
||||
* Decorator used for rendering simple item pages by type and viewMode (and optionally a representationType)
|
||||
* @param type
|
||||
* @param viewMode
|
||||
* @param representationType
|
||||
*/
|
||||
export function rendersItemType(type: string, viewMode: ElementViewMode) {
|
||||
export function rendersItemType(type: string, viewMode: string, representationType?: MetadataRepresentationType) {
|
||||
return function decorator(component: any) {
|
||||
if (hasNoValue(map.get(viewMode))) {
|
||||
map.set(viewMode, new Map());
|
||||
}
|
||||
if (hasValue(map.get(viewMode).get(type))) {
|
||||
throw new Error(`There can't be more than one component to render Items of type "${type}" in view mode "${viewMode}"`);
|
||||
if (hasNoValue(map.get(viewMode).get(type))) {
|
||||
map.get(viewMode).set(type, new Map());
|
||||
}
|
||||
map.get(viewMode).set(type, component);
|
||||
if (hasNoValue(representationType)) {
|
||||
representationType = NO_REPRESENTATION_TYPE;
|
||||
}
|
||||
if (hasValue(map.get(viewMode).get(type).get(representationType))) {
|
||||
throw new Error(`There can't be more than one component to render Metadata of type "${type}" in view mode "${viewMode}" with representation type "${representationType}"`);
|
||||
}
|
||||
map.get(viewMode).get(type).set(representationType, component);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component used for rendering an item by type and viewMode
|
||||
* Get the component used for rendering an item by type and viewMode (and optionally a representationType)
|
||||
* @param type
|
||||
* @param viewMode
|
||||
* @param representationType
|
||||
*/
|
||||
export function getComponentByItemType(type: string, viewMode: ElementViewMode) {
|
||||
let component = map.get(viewMode).get(type);
|
||||
if (hasNoValue(component)) {
|
||||
component = map.get(viewMode).get(DEFAULT_ITEM_TYPE);
|
||||
export function getComponentByItemType(type: string, viewMode: string, representationType?: MetadataRepresentationType) {
|
||||
if (hasNoValue(representationType)) {
|
||||
representationType = NO_REPRESENTATION_TYPE;
|
||||
}
|
||||
return component;
|
||||
if (hasNoValue(map.get(viewMode))) {
|
||||
viewMode = DEFAULT_VIEW_MODE;
|
||||
}
|
||||
if (hasNoValue(map.get(viewMode).get(type))) {
|
||||
type = DEFAULT_ITEM_TYPE;
|
||||
}
|
||||
let representationComponent = map.get(viewMode).get(type).get(representationType);
|
||||
if (hasNoValue(representationComponent)) {
|
||||
representationComponent = map.get(viewMode).get(type).get(DEFAULT_REPRESENTATION_TYPE);
|
||||
}
|
||||
if (hasNoValue(representationComponent)) {
|
||||
representationComponent = map.get(viewMode).get(type).get(NO_REPRESENTATION_TYPE);
|
||||
}
|
||||
return representationComponent;
|
||||
}
|
||||
|
@@ -8,8 +8,10 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import * as decorator from '../item-type-decorator';
|
||||
import { getComponentByItemType } from '../item-type-decorator';
|
||||
import { ElementViewMode } from '../../view-mode';
|
||||
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||
import createSpy = jasmine.createSpy;
|
||||
import { VIEW_MODE_FULL } from '../../../+item-page/simple/item-page.component';
|
||||
import { VIEW_MODE_METADATA } from '../../../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
||||
|
||||
const relationType = 'type';
|
||||
const mockItem: Item = Object.assign(new Item(), {
|
||||
@@ -26,7 +28,8 @@ const mockItem: Item = Object.assign(new Item(), {
|
||||
value: relationType
|
||||
}]
|
||||
});
|
||||
const viewMode = ElementViewMode.Full;
|
||||
const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(relationType), mockItem);
|
||||
let viewMode = VIEW_MODE_FULL;
|
||||
|
||||
describe('ItemTypeSwitcherComponent', () => {
|
||||
let comp: ItemTypeSwitcherComponent;
|
||||
@@ -47,6 +50,13 @@ describe('ItemTypeSwitcherComponent', () => {
|
||||
spyOnProperty(decorator, 'getComponentByItemType').and.returnValue(createSpy('getComponentByItemType'))
|
||||
}));
|
||||
|
||||
describe('when the injected object is of type Item', () => {
|
||||
beforeEach(() => {
|
||||
viewMode = VIEW_MODE_FULL;
|
||||
comp.object = mockItem;
|
||||
comp.viewMode = viewMode;
|
||||
});
|
||||
|
||||
describe('when calling getComponent', () => {
|
||||
beforeEach(() => {
|
||||
comp.getComponent();
|
||||
@@ -56,5 +66,24 @@ describe('ItemTypeSwitcherComponent', () => {
|
||||
expect(decorator.getComponentByItemType).toHaveBeenCalledWith(relationType, viewMode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the injected object is of type MetadataRepresentation', () => {
|
||||
beforeEach(() => {
|
||||
viewMode = VIEW_MODE_METADATA;
|
||||
comp.object = mockItemMetadataRepresentation;
|
||||
comp.viewMode = viewMode;
|
||||
});
|
||||
|
||||
describe('when calling getComponent', () => {
|
||||
beforeEach(() => {
|
||||
comp.getComponent();
|
||||
});
|
||||
|
||||
it('should call getComponentByItemType with parameters type, viewMode and representationType', () => {
|
||||
expect(decorator.getComponentByItemType).toHaveBeenCalledWith(relationType, viewMode, mockItemMetadataRepresentation.representationType);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -4,7 +4,7 @@ import { Item } from '../../../core/shared/item.model';
|
||||
import { hasValue } from '../../empty.util';
|
||||
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
|
||||
import { getComponentByItemType } from '../item-type-decorator';
|
||||
import { ElementViewMode } from '../../view-mode';
|
||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
|
||||
export const ITEM: InjectionToken<string> = new InjectionToken<string>('item');
|
||||
|
||||
@@ -18,14 +18,14 @@ export const ITEM: InjectionToken<string> = new InjectionToken<string>('item');
|
||||
*/
|
||||
export class ItemTypeSwitcherComponent implements OnInit {
|
||||
/**
|
||||
* The item to determine the component for
|
||||
* The item or metadata to determine the component for
|
||||
*/
|
||||
@Input() object: Item | SearchResult<Item>;
|
||||
@Input() object: Item | SearchResult<Item> | MetadataRepresentation;
|
||||
|
||||
/**
|
||||
* The preferred view-mode to display
|
||||
*/
|
||||
@Input() viewMode: ElementViewMode;
|
||||
@Input() viewMode: string;
|
||||
|
||||
/**
|
||||
* The object injector used to inject the item into the child component
|
||||
@@ -48,6 +48,11 @@ export class ItemTypeSwitcherComponent implements OnInit {
|
||||
* @returns {string}
|
||||
*/
|
||||
getComponent(): string {
|
||||
if (hasValue((this.object as any).representationType)) {
|
||||
const metadataRepresentation = this.object as MetadataRepresentation;
|
||||
return getComponentByItemType(metadataRepresentation.itemType, this.viewMode, metadataRepresentation.representationType);
|
||||
}
|
||||
|
||||
let item: Item;
|
||||
if (hasValue((this.object as any).dspaceObject)) {
|
||||
const searchResult = this.object as ItemSearchResult;
|
||||
|
@@ -1 +1 @@
|
||||
<ds-item-type-switcher [object]="object" [viewMode]="ElementViewMode.SetElement"></ds-item-type-switcher>
|
||||
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import * as viewMode from '../../../shared/view-mode';
|
||||
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { SetViewMode } from '../../view-mode';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-list-element',
|
||||
@@ -18,5 +18,5 @@ import { SetViewMode } from '../../view-mode';
|
||||
*/
|
||||
@renderElementsFor(Item, SetViewMode.List)
|
||||
export class ItemListElementComponent extends AbstractListableElementComponent<Item> {
|
||||
ElementViewMode = viewMode.ElementViewMode;
|
||||
viewMode = VIEW_MODE_ELEMENT;
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('JournalIssue', ElementViewMode.SetElement)
|
||||
@rendersItemType('JournalIssue', VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-journal-issue-list-element',
|
||||
styleUrls: ['./journal-issue-list-element.component.scss'],
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('JournalVolume', ElementViewMode.SetElement)
|
||||
@rendersItemType('JournalVolume', VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-journal-volume-list-element',
|
||||
styleUrls: ['./journal-volume-list-element.component.scss'],
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('Journal', ElementViewMode.SetElement)
|
||||
@rendersItemType('Journal', VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-journal-list-element',
|
||||
styleUrls: ['./journal-list-element.component.scss'],
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('OrgUnit', ElementViewMode.SetElement)
|
||||
@rendersItemType('OrgUnit', VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-orgunit-list-element',
|
||||
styleUrls: ['./orgunit-list-element.component.scss'],
|
||||
|
@@ -13,4 +13,3 @@
|
||||
</ds-truncatable-part>
|
||||
</span>
|
||||
</ds-truncatable>
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('Person', ElementViewMode.SetElement)
|
||||
@rendersItemType('Person', VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-person-list-element',
|
||||
styleUrls: ['./person-list-element.component.scss'],
|
||||
|
@@ -0,0 +1,15 @@
|
||||
<ng-template #descTemplate>
|
||||
<span class="text-muted">
|
||||
<span *ngIf="item.filterMetadata(['person.identifier.jobtitle']).length > 0"
|
||||
class="item-list-job-title">
|
||||
<span *ngFor="let value of getValues(['person.identifier.jobtitle']); let last=last;">
|
||||
<span [innerHTML]="value"><span [innerHTML]="value"></span></span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ds-truncatable [id]="item.id">
|
||||
<a [routerLink]="['/items/' + item.id]"
|
||||
[innerHTML]="getFirstValue('dc.contributor.author')"
|
||||
[tooltip]="descTemplate"></a>
|
||||
</ds-truncatable>
|
@@ -0,0 +1,16 @@
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
import { Component } from '@angular/core';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { MetadataRepresentationType } from '../../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
|
||||
@rendersItemType('Person', VIEW_MODE_ELEMENT, MetadataRepresentationType.Item)
|
||||
@Component({
|
||||
selector: 'ds-person-metadata-list-element',
|
||||
templateUrl: './person-metadata-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* The component for displaying a list element for an item of the type Person
|
||||
*/
|
||||
export class PersonMetadataListElementComponent extends TypedItemSearchResultListElementComponent {
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('Project', ElementViewMode.SetElement)
|
||||
@rendersItemType('Project', VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-project-list-element',
|
||||
styleUrls: ['./project-list-element.component.scss'],
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { DEFAULT_ITEM_TYPE, rendersItemType } from '../../../../items/item-type-decorator';
|
||||
import { ElementViewMode } from '../../../../view-mode';
|
||||
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType('Publication', ElementViewMode.SetElement)
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, ElementViewMode.SetElement)
|
||||
@rendersItemType('Publication', VIEW_MODE_ELEMENT)
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_ELEMENT)
|
||||
@Component({
|
||||
selector: 'ds-publication-list-element',
|
||||
styleUrls: ['./publication-list-element.component.scss'],
|
||||
|
@@ -0,0 +1,2 @@
|
||||
<ds-item-type-switcher [object]="metadataRepresentation" [viewMode]="viewMode">
|
||||
</ds-item-type-switcher>
|
@@ -0,0 +1,38 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ITEM } from '../../../items/switcher/item-type-switcher.component';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ItemMetadataListElementComponent } from './item-metadata-list-element.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||
|
||||
const mockItemMetadataRepresentation = new ItemMetadataRepresentation('type');
|
||||
|
||||
describe('ItemMetadataListElementComponent', () => {
|
||||
let comp: ItemMetadataListElementComponent;
|
||||
let fixture: ComponentFixture<ItemMetadataListElementComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
declarations: [ItemMetadataListElementComponent],
|
||||
providers: [
|
||||
{ provide: ITEM, useValue: mockItemMetadataRepresentation }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(ItemMetadataListElementComponent, {
|
||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(ItemMetadataListElementComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should call an item-type-switcher component and pass the item-metadata-representation', () => {
|
||||
const itemTypeSwitcher = fixture.debugElement.query(By.css('ds-item-type-switcher')).nativeElement;
|
||||
expect(itemTypeSwitcher.object).toBe(mockItemMetadataRepresentation);
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,25 @@
|
||||
import * as viewMode from '../../../view-mode';
|
||||
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component';
|
||||
import { DEFAULT_ITEM_TYPE, rendersItemType } from '../../../items/item-type-decorator';
|
||||
import { VIEW_MODE_METADATA } from '../../../../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_METADATA, MetadataRepresentationType.Item)
|
||||
@Component({
|
||||
selector: 'ds-item-metadata-list-element',
|
||||
templateUrl: './item-metadata-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* A component for displaying MetadataRepresentation objects in the form of items
|
||||
* It will send the MetadataRepresentation object along with ElementViewMode.SetElement to the ItemTypeSwitcherComponent,
|
||||
* which will in his turn decide how to render the item as metadata.
|
||||
*/
|
||||
export class ItemMetadataListElementComponent extends MetadataRepresentationListElementComponent {
|
||||
/**
|
||||
* The view-mode we're currently on
|
||||
* @type {ElementViewMode}
|
||||
*/
|
||||
viewMode = VIEW_MODE_ELEMENT;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { ITEM } from '../../items/switcher/item-type-switcher.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-representation-list-element',
|
||||
template: ''
|
||||
})
|
||||
/**
|
||||
* An abstract class for displaying a single MetadataRepresentation
|
||||
*/
|
||||
export class MetadataRepresentationListElementComponent {
|
||||
constructor(@Inject(ITEM) public metadataRepresentation: MetadataRepresentation) {
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<span>{{metadataRepresentation.getPrimaryValue()}}</span>
|
||||
</div>
|
@@ -0,0 +1,39 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { PlainTextMetadataListElementComponent } from './plain-text-metadata-list-element.component';
|
||||
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||
import { ITEM } from '../../../items/switcher/item-type-switcher.component';
|
||||
|
||||
const mockMetadataRepresentation = Object.assign(new MetadatumRepresentation('type'), {
|
||||
key: 'dc.contributor.author',
|
||||
value: 'Test Author'
|
||||
});
|
||||
|
||||
describe('PlainTextMetadataListElementComponent', () => {
|
||||
let comp: PlainTextMetadataListElementComponent;
|
||||
let fixture: ComponentFixture<PlainTextMetadataListElementComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
declarations: [PlainTextMetadataListElementComponent],
|
||||
providers: [
|
||||
{ provide: ITEM, useValue: mockMetadataRepresentation }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(PlainTextMetadataListElementComponent, {
|
||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(PlainTextMetadataListElementComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should contain the value as plain text', () => {
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain(mockMetadataRepresentation.value);
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,19 @@
|
||||
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { Component } from '@angular/core';
|
||||
import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component';
|
||||
import { DEFAULT_ITEM_TYPE, rendersItemType } from '../../../items/item-type-decorator';
|
||||
import { VIEW_MODE_METADATA } from '../../../../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
||||
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_METADATA, MetadataRepresentationType.PlainText)
|
||||
// For now, authority controlled fields are rendered the same way as plain text fields
|
||||
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_METADATA, MetadataRepresentationType.AuthorityControlled)
|
||||
@Component({
|
||||
selector: 'ds-plain-text-metadata-list-element',
|
||||
templateUrl: './plain-text-metadata-list-element.component.html'
|
||||
})
|
||||
/**
|
||||
* A component for displaying MetadataRepresentation objects in the form of plain text
|
||||
* It will simply use the value retrieved from MetadataRepresentation.getPrimaryValue() to display as plain text
|
||||
*/
|
||||
export class PlainTextMetadataListElementComponent extends MetadataRepresentationListElementComponent {
|
||||
}
|
@@ -1 +1 @@
|
||||
<ds-item-type-switcher [object]="object" [viewMode]="ElementViewMode.SetElement"></ds-item-type-switcher>
|
||||
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>
|
||||
|
@@ -4,9 +4,9 @@ import { focusBackground } from '../../../animations/focus';
|
||||
|
||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||
import * as viewMode from '../../../../shared/view-mode';
|
||||
import { SetViewMode } from '../../../view-mode';
|
||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||
import { VIEW_MODE_ELEMENT } from '../../../../+item-page/simple/related-items/related-items-component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-search-result-list-element',
|
||||
@@ -18,5 +18,5 @@ import { SearchResultListElementComponent } from '../search-result-list-element.
|
||||
|
||||
@renderElementsFor(ItemSearchResult, SetViewMode.List)
|
||||
export class ItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
|
||||
ElementViewMode = viewMode.ElementViewMode;
|
||||
viewMode = VIEW_MODE_ELEMENT;
|
||||
}
|
||||
|
@@ -94,6 +94,11 @@ import { CapitalizePipe } from './utils/capitalize.pipe';
|
||||
import { ObjectKeysPipe } from './utils/object-keys-pipe';
|
||||
import { MomentModule } from 'ngx-moment';
|
||||
import { NouisliderModule } from 'ng2-nouislider';
|
||||
import { PlainTextMetadataListElementComponent } from './object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
|
||||
import { ItemMetadataListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-list-element.component';
|
||||
import { TooltipModule } from 'ngx-bootstrap';
|
||||
import { PersonMetadataListElementComponent } from './object-list/item-list-element/item-types/person/person-metadata-list-element.component';
|
||||
import { MetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/metadata-representation-list-element.component';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -117,6 +122,10 @@ const MODULES = [
|
||||
TextMaskModule
|
||||
];
|
||||
|
||||
const ROOT_MODULES = [
|
||||
TooltipModule.forRoot()
|
||||
];
|
||||
|
||||
const PIPES = [
|
||||
// put shared pipes here
|
||||
EnumKeysPipe,
|
||||
@@ -184,12 +193,16 @@ const ENTRY_COMPONENTS = [
|
||||
SearchResultGridElementComponent,
|
||||
PublicationListElementComponent,
|
||||
PersonListElementComponent,
|
||||
PersonMetadataListElementComponent,
|
||||
OrgUnitListElementComponent,
|
||||
ProjectListElementComponent,
|
||||
JournalListElementComponent,
|
||||
JournalVolumeListElementComponent,
|
||||
JournalIssueListElementComponent,
|
||||
BrowseEntryListElementComponent
|
||||
BrowseEntryListElementComponent,
|
||||
PlainTextMetadataListElementComponent,
|
||||
ItemMetadataListElementComponent,
|
||||
MetadataRepresentationListElementComponent
|
||||
];
|
||||
|
||||
const PROVIDERS = [
|
||||
@@ -206,7 +219,8 @@ const DIRECTIVES = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
...MODULES
|
||||
...MODULES,
|
||||
...ROOT_MODULES
|
||||
],
|
||||
declarations: [
|
||||
...PIPES,
|
||||
|
@@ -8,17 +8,7 @@ export enum SetViewMode {
|
||||
Grid = 'grid'
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum used for defining the view-mode of a single element
|
||||
* Full Display the full element
|
||||
* SetElement Display the element as part of a set
|
||||
*/
|
||||
export enum ElementViewMode {
|
||||
Full,
|
||||
SetElement
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewMode refers to either a SetViewMode or ElementViewMode
|
||||
*/
|
||||
export type ViewMode = SetViewMode | ElementViewMode;
|
||||
export type ViewMode = SetViewMode;
|
||||
|
Reference in New Issue
Block a user