Merge pull request #354 from atmire/mixing-text-authority-entities

[Configurable Entities] Mixing text fields, authority controlled fields and typed items
This commit is contained in:
Tim Donohue
2019-03-15 12:38:45 -05:00
committed by GitHub
127 changed files with 1388 additions and 574 deletions

View File

@@ -346,7 +346,7 @@
"f.dateIssued.max": "End date",
"f.subject": "Subject",
"f.has_content_in_original_bundle": "Has files",
"f.entityType": "Entity Type"
"f.entityType": "Item Type"
},
"filter": {
"show-more": "Show more",
@@ -376,8 +376,8 @@
"head": "Has files"
},
"entityType": {
"placeholder": "Entity Type",
"head": "Entity Type"
"placeholder": "Item Type",
"head": "Item Type"
}
}
}

View File

@@ -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';

View File

@@ -19,17 +19,18 @@ import { FileSectionComponent } from './simple/field-components/file-section/fil
import { CollectionsComponent } from './field-components/collections/collections.component';
import { FullItemPageComponent } from './full/full-item-page.component';
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
import { RelatedEntitiesComponent } from './simple/related-entities/related-entities-component';
import { RelatedItemsComponent } from './simple/related-items/related-items-component';
import { SearchPageModule } from '../+search-page/search-page.module';
import { PublicationComponent } from './simple/entity-types/publication/publication.component';
import { PersonComponent } from './simple/entity-types/person/person.component';
import { OrgunitComponent } from './simple/entity-types/orgunit/orgunit.component';
import { ProjectComponent } from './simple/entity-types/project/project.component';
import { JournalComponent } from './simple/entity-types/journal/journal.component';
import { JournalVolumeComponent } from './simple/entity-types/journal-volume/journal-volume.component';
import { JournalIssueComponent } from './simple/entity-types/journal-issue/journal-issue.component';
import { EntityComponent } from './simple/entity-types/shared/entity.component';
import { PublicationComponent } from './simple/item-types/publication/publication.component';
import { PersonComponent } from './simple/item-types/person/person.component';
import { OrgunitComponent } from './simple/item-types/orgunit/orgunit.component';
import { ProjectComponent } from './simple/item-types/project/project.component';
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 { EditItemPageModule } from './edit-item-page/edit-item-page.module';
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
@NgModule({
@@ -59,12 +60,13 @@ import { RelatedEntitiesSearchComponent } from './simple/related-entities/relate
ProjectComponent,
OrgunitComponent,
PersonComponent,
RelatedEntitiesComponent,
EntityComponent,
RelatedItemsComponent,
ItemComponent,
GenericItemPageFieldComponent,
JournalComponent,
JournalIssueComponent,
JournalVolumeComponent,
MetadataRepresentationListComponent,
RelatedEntitiesSearchComponent
],
entryComponents: [

View File

@@ -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-entity-type-switcher [object]="item" [viewMode]="ElementViewMode.Full"></ds-entity-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>

View File

@@ -8,14 +8,13 @@ import { ActivatedRoute } from '@angular/router';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
import { MetadataService } from '../../core/metadata/metadata.service';
import { VarDirective } from '../../shared/utils/var.directive';
import { Observable } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data';
import { Item } from '../../core/shared/item.model';
import { PaginatedList } from '../../core/data/paginated-list';
import { PageInfo } from '../../core/shared/page-info.model';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { createRelationshipsObservable } from './entity-types/shared/entity.component.spec';
import { createRelationshipsObservable } from './item-types/shared/item.component.spec';
import { of as observableOf } from 'rxjs';
const mockItem: Item = Object.assign(new Item(), {

View File

@@ -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,

View File

@@ -24,15 +24,15 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="volumes$ | async"
<ds-related-items
[items]="volumes$ | async"
[label]="'relationships.isSingleVolumeOf' | translate">
</ds-related-entities>
<ds-related-entities
</ds-related-items>
<ds-related-items
class="mb-1 mt-1"
[entities]="publications$ | async"
[items]="publications$ | async"
[label]="'relationships.isPublicationOfJournalIssue' | translate">
</ds-related-entities>
</ds-related-items>
<ds-generic-item-page-field [item]="item"
[fields]="['journalissue.identifier.description']"
[label]="'journalissue.page.description'">

View File

@@ -1,9 +1,8 @@
import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { createRelationshipsObservable, getEntityPageFieldsTest } from '../shared/entity.component.spec';
import { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
import { JournalIssueComponent } from './journal-issue.component';
import { of as observableOf } from 'rxjs';
@@ -33,4 +32,4 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
describe('JournalIssueComponent', getEntityPageFieldsTest(mockItem, JournalIssueComponent));
describe('JournalIssueComponent', getItemPageFieldsTest(mockItem, JournalIssueComponent));

View File

@@ -2,22 +2,22 @@ import { Component, Inject } from '@angular/core';
import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersEntityType } from '../../../../shared/entities/entity-type-decorator';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersEntityType('JournalIssue', ElementViewMode.Full)
@rendersItemType('JournalIssue', VIEW_MODE_FULL)
@Component({
selector: 'ds-journal-issue',
styleUrls: ['./journal-issue.component.scss'],
templateUrl: './journal-issue.component.html'
})
/**
* The component for displaying metadata and relations of an item with entity type Journal Issue
* The component for displaying metadata and relations of an item of the type Journal Issue
*/
export class JournalIssueComponent extends EntityComponent {
export class JournalIssueComponent extends ItemComponent {
/**
* The volumes related to this journal issue
*/

View File

@@ -16,14 +16,14 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="journals$ | async"
<ds-related-items
[items]="journals$ | async"
[label]="'relationships.isSingleJournalOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="issues$ | async"
</ds-related-items>
<ds-related-items
[items]="issues$ | async"
[label]="'relationships.isIssueOf' | translate">
</ds-related-entities>
</ds-related-items>
<ds-generic-item-page-field [item]="item"
[fields]="['journalvolume.identifier.description']"
[label]="'journalvolume.page.description'">

View File

@@ -1,9 +1,8 @@
import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { createRelationshipsObservable, getEntityPageFieldsTest } from '../shared/entity.component.spec';
import { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
import { JournalVolumeComponent } from './journal-volume.component';
import { of as observableOf } from 'rxjs';
@@ -28,4 +27,4 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
describe('JournalVolumeComponent', getEntityPageFieldsTest(mockItem, JournalVolumeComponent));
describe('JournalVolumeComponent', getItemPageFieldsTest(mockItem, JournalVolumeComponent));

View File

@@ -2,22 +2,22 @@ import { Component, Inject } from '@angular/core';
import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersEntityType } from '../../../../shared/entities/entity-type-decorator';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersEntityType('JournalVolume', ElementViewMode.Full)
@rendersItemType('JournalVolume', VIEW_MODE_FULL)
@Component({
selector: 'ds-journal-volume',
styleUrls: ['./journal-volume.component.scss'],
templateUrl: './journal-volume.component.html'
})
/**
* The component for displaying metadata and relations of an item with entity type Journal Volume
* The component for displaying metadata and relations of an item of the type Journal Volume
*/
export class JournalVolumeComponent extends EntityComponent {
export class JournalVolumeComponent extends ItemComponent {
/**
* The journals related to this journal volume
*/

View File

@@ -20,10 +20,10 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="volumes$ | async"
<ds-related-items
[items]="volumes$ | async"
[label]="'relationships.isVolumeOf' | translate">
</ds-related-entities>
</ds-related-items>
<ds-generic-item-page-field class="item-page-fields" [item]="item"
[fields]="['journal.identifier.description']"
[label]="'journal.page.description'">

View File

@@ -1,10 +1,9 @@
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { By } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';

View File

@@ -2,22 +2,22 @@ import { Component, Inject } from '@angular/core';
import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersEntityType } from '../../../../shared/entities/entity-type-decorator';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersEntityType('Journal', ElementViewMode.Full)
@rendersItemType('Journal', VIEW_MODE_FULL)
@Component({
selector: 'ds-journal',
styleUrls: ['./journal.component.scss'],
templateUrl: './journal.component.html'
})
/**
* The component for displaying metadata and relations of an item with entity type Journal
* The component for displaying metadata and relations of an item of the type Journal
*/
export class JournalComponent extends EntityComponent {
export class JournalComponent extends ItemComponent {
/**
* The volumes related to this journal
*/

View File

@@ -24,18 +24,18 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="people$ | async"
<ds-related-items
[items]="people$ | async"
[label]="'relationships.isPersonOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="projects$ | async"
</ds-related-items>
<ds-related-items
[items]="projects$ | async"
[label]="'relationships.isProjectOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="publications$ | async"
</ds-related-items>
<ds-related-items
[items]="publications$ | async"
[label]="'relationships.isPublicationOf' | translate">
</ds-related-entities>
</ds-related-items>
<ds-generic-item-page-field [item]="item"
[fields]="['orgunit.identifier.description']"
[label]="'orgunit.page.description'">

View File

@@ -1,9 +1,8 @@
import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { createRelationshipsObservable, getEntityPageFieldsTest } from '../shared/entity.component.spec';
import { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
import { OrgunitComponent } from './orgunit.component';
import { of as observableOf } from 'rxjs';
@@ -38,4 +37,4 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
describe('OrgUnitComponent', getEntityPageFieldsTest(mockItem, OrgunitComponent));
describe('OrgUnitComponent', getItemPageFieldsTest(mockItem, OrgunitComponent));

View File

@@ -2,22 +2,22 @@ import { Component, Inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersEntityType } from '../../../../shared/entities/entity-type-decorator';
import { ElementViewMode } from '../../../../shared/view-mode';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersEntityType('OrgUnit', ElementViewMode.Full)
@rendersItemType('OrgUnit', VIEW_MODE_FULL)
@Component({
selector: 'ds-orgunit',
styleUrls: ['./orgunit.component.scss'],
templateUrl: './orgunit.component.html'
})
/**
* The component for displaying metadata and relations of an item with entity type Organisation Unit
* The component for displaying metadata and relations of an item of the type Organisation Unit
*/
export class OrgunitComponent extends EntityComponent implements OnInit {
export class OrgunitComponent extends ItemComponent implements OnInit {
/**
* The people related to this organisation unit
*/

View File

@@ -24,14 +24,14 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="projects$ | async"
<ds-related-items
[items]="projects$ | async"
[label]="'relationships.isProjectOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="orgUnits$ | async"
</ds-related-items>
<ds-related-items
[items]="orgUnits$ | async"
[label]="'relationships.isOrgUnitOf' | translate">
</ds-related-entities>
</ds-related-items>
<ds-generic-item-page-field [item]="item"
[fields]="['person.identifier.jobtitle']"
[label]="'person.page.jobtitle'">

View File

@@ -1,9 +1,8 @@
import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { createRelationshipsObservable, getEntityPageFieldsTest } from '../shared/entity.component.spec';
import { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
import { PersonComponent } from './person.component';
import { of as observableOf } from 'rxjs';
@@ -48,4 +47,4 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
describe('PersonComponent', getEntityPageFieldsTest(mockItem, PersonComponent));
describe('PersonComponent', getItemPageFieldsTest(mockItem, PersonComponent));

View File

@@ -2,23 +2,23 @@ import { Component, Inject } from '@angular/core';
import { Observable , of as observableOf } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersEntityType } from '../../../../shared/entities/entity-type-decorator';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { isNotEmpty } from '../../../../shared/empty.util';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersEntityType('Person', ElementViewMode.Full)
@rendersItemType('Person', VIEW_MODE_FULL)
@Component({
selector: 'ds-person',
styleUrls: ['./person.component.scss'],
templateUrl: './person.component.html'
})
/**
* The component for displaying metadata and relations of an item with entity type Person
* The component for displaying metadata and relations of an item of the type Person
*/
export class PersonComponent extends EntityComponent {
export class PersonComponent extends ItemComponent {
/**
* The publications related to this person
*/

View File

@@ -20,18 +20,18 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="people$ | async"
<ds-related-items
[items]="people$ | async"
[label]="'relationships.isPersonOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="publications$ | async"
</ds-related-items>
<ds-related-items
[items]="publications$ | async"
[label]="'relationships.isPublicationOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="orgUnits$ | async"
</ds-related-items>
<ds-related-items
[items]="orgUnits$ | async"
[label]="'relationships.isOrgUnitOf' | translate">
</ds-related-entities>
</ds-related-items>
<ds-generic-item-page-field [item]="item"
[fields]="['project.identifier.description']"
[label]="'project.page.description'">

View File

@@ -1,9 +1,8 @@
import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { createRelationshipsObservable, getEntityPageFieldsTest } from '../shared/entity.component.spec';
import { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
import { ProjectComponent } from './project.component';
import { of as observableOf } from 'rxjs';
@@ -38,4 +37,4 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
describe('ProjectComponent', getEntityPageFieldsTest(mockItem, ProjectComponent));
describe('ProjectComponent', getItemPageFieldsTest(mockItem, ProjectComponent));

View File

@@ -2,22 +2,22 @@ import { Component, Inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersEntityType } from '../../../../shared/entities/entity-type-decorator';
import { ElementViewMode } from '../../../../shared/view-mode';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersEntityType('Project', ElementViewMode.Full)
@rendersItemType('Project', VIEW_MODE_FULL)
@Component({
selector: 'ds-project',
styleUrls: ['./project.component.scss'],
templateUrl: './project.component.html'
})
/**
* The component for displaying metadata and relations of an item with entity type Project
* The component for displaying metadata and relations of an item of the type Project
*/
export class ProjectComponent extends EntityComponent implements OnInit {
export class ProjectComponent extends ItemComponent implements OnInit {
/**
* The people related to this project
*/

View File

@@ -21,22 +21,22 @@
</ds-generic-item-page-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-entities
[entities]="authors$ | async"
[label]="'relationships.isAuthorOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="projects$ | async"
<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">
</ds-related-entities>
<ds-related-entities
[entities]="orgUnits$ | async"
</ds-related-items>
<ds-related-items
[items]="orgUnits$ | async"
[label]="'relationships.isOrgUnitOf' | translate">
</ds-related-entities>
<ds-related-entities
[entities]="journalIssues$ | async"
</ds-related-items>
<ds-related-items
[items]="journalIssues$ | async"
[label]="'relationships.isJournalIssueOf' | translate">
</ds-related-entities>
</ds-related-items>
<ds-item-page-abstract-field [item]="item"></ds-item-page-abstract-field>
<ds-generic-item-page-field [item]="item"
[fields]="['dc.subject']"

View File

@@ -3,18 +3,17 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { By } from '@angular/platform-browser';
import { createRelationshipsObservable } from '../shared/entity.component.spec';
import { createRelationshipsObservable } from '../shared/item.component.spec';
import { PublicationComponent } from './publication.component';
import { of as observableOf } from 'rxjs';

View File

@@ -3,26 +3,27 @@ import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import {
DEFAULT_ENTITY_TYPE,
rendersEntityType
} from '../../../../shared/entities/entity-type-decorator';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { EntityComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/entity.component';
DEFAULT_ITEM_TYPE,
rendersItemType
} from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
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';
@rendersEntityType('Publication', ElementViewMode.Full)
@rendersEntityType(DEFAULT_ENTITY_TYPE, ElementViewMode.Full)
@rendersItemType('Publication', VIEW_MODE_FULL)
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_FULL)
@Component({
selector: 'ds-publication',
styleUrls: ['./publication.component.scss'],
templateUrl: './publication.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PublicationComponent extends EntityComponent implements OnInit {
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 EntityComponent 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'),

View File

@@ -7,27 +7,36 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { isNotEmpty } from '../../../../shared/empty.util';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { RelationshipType } from '../../../../core/shared/entities/relationship-type.model';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data';
import { Relationship } from '../../../../core/shared/entities/relationship.model';
import { Observable } from 'rxjs';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { compareArraysUsing, compareArraysUsingIds } from './entity.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 entity-page-fields component using a mockItem and the type of component
* Create a generic test for an item-page-fields component using a mockItem and the type of component
* @param {Item} mockItem The item to use for testing. The item needs to contain just the metadata necessary to
* execute the tests for it's component.
* @param component The type of component to create test cases for.
* @returns {() => void} Returns a specDefinition for the test.
*/
export function getEntityPageFieldsTest(mockItem: Item, component) {
export function getItemPageFieldsTest(mockItem: Item, component) {
return () => {
let comp: any;
let fixture: ComponentFixture<any>;
@@ -101,7 +110,7 @@ export function createRelationshipsObservable() {
})
])));
}
describe('EntityComponent', () => {
describe('ItemComponent', () => {
const arr1 = [
{
id: 1,
@@ -307,4 +316,116 @@ describe('EntityComponent', () => {
});
});
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].getValue()).toEqual('First value');
expect(reps[1].getValue()).toEqual('Second value');
expect(reps[2].getValue()).toEqual('related item');
expect(reps[3].getValue()).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);
});
});
})
});

View File

@@ -4,12 +4,17 @@ import { distinctUntilChanged, filter, flatMap, map } from 'rxjs/operators';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data';
import { RelationshipType } from '../../../../core/shared/entities/relationship-type.model';
import { Relationship } from '../../../../core/shared/entities/relationship.model';
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 { ITEM } from '../../../../shared/entities/switcher/entity-type-switcher.component';
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,18 +87,54 @@ 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: any) => Object.assign(new Metadatum(), metadatum))
.map((metadatum: Metadatum) => {
if (metadatum.isVirtual) {
const matchingRels = rels.filter((rel: Relationship) => ('' + rel.id) === metadatum.virtualValue);
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-entity',
selector: 'ds-item',
template: ''
})
/**
* A generic component for displaying metadata and relations of an item
*/
export class EntityComponent implements OnInit {
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 +166,25 @@ export class EntityComponent 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)
);
}
}

View File

@@ -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>

View File

@@ -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);
});
});

View File

@@ -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;
}

View File

@@ -1,30 +0,0 @@
import { Component, Input } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import * as viewMode from '../../../shared/view-mode';
@Component({
selector: 'ds-related-entities',
styleUrls: ['./related-entities.component.scss'],
templateUrl: './related-entities.component.html'
})
/**
* This component is used for displaying relations between entities
* It expects a list of entities to display and a label to put on top
*/
export class RelatedEntitiesComponent {
/**
* A list of entities to display
*/
@Input() entities: Item[];
/**
* An i18n label to use as a title for the list (usually describes the relation)
*/
@Input() label: string;
/**
* The view-mode we're currently on
* @type {ElementViewMode}
*/
ElementViewMode = viewMode.ElementViewMode
}

View File

@@ -1,5 +0,0 @@
<ds-metadata-field-wrapper *ngIf="entities && entities.length > 0" [label]="label">
<ds-entity-type-switcher *ngFor="let entity of entities"
[object]="entity" [viewMode]="ElementViewMode.SetElement">
</ds-entity-type-switcher>
</ds-metadata-field-wrapper>

View File

@@ -0,0 +1,31 @@
import { Component, Input } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
export const VIEW_MODE_ELEMENT = 'element';
@Component({
selector: 'ds-related-items',
styleUrls: ['./related-items.component.scss'],
templateUrl: './related-items.component.html'
})
/**
* This component is used for displaying relations between items
* It expects a list of items to display and a label to put on top
*/
export class RelatedItemsComponent {
/**
* A list of items to display
*/
@Input() items: Item[];
/**
* An i18n label to use as a title for the list (usually describes the relation)
*/
@Input() label: string;
/**
* The view-mode we're currently on
* @type {ElementViewMode}
*/
viewMode = VIEW_MODE_ELEMENT;
}

View File

@@ -0,0 +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]="viewMode">
</ds-item-type-switcher>
</ds-metadata-field-wrapper>

View File

@@ -1,12 +1,12 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { RelatedEntitiesComponent } from './related-entities-component';
import { RelatedItemsComponent } from './related-items-component';
import { Item } from '../../../core/shared/item.model';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { PageInfo } from '../../../core/shared/page-info.model';
import { By } from '@angular/platform-browser';
import { createRelationshipsObservable } from '../entity-types/shared/entity.component.spec';
import { createRelationshipsObservable } from '../item-types/shared/item.component.spec';
import { of as observableOf } from 'rxjs';
const mockItem1: Item = Object.assign(new Item(), {
@@ -19,33 +19,33 @@ const mockItem2: Item = Object.assign(new Item(), {
metadata: [],
relationships: createRelationshipsObservable()
});
const mockEntities = [mockItem1, mockItem2];
const mockItems = [mockItem1, mockItem2];
describe('RelatedEntitiesComponent', () => {
let comp: RelatedEntitiesComponent;
let fixture: ComponentFixture<RelatedEntitiesComponent>;
describe('RelatedItemsComponent', () => {
let comp: RelatedItemsComponent;
let fixture: ComponentFixture<RelatedItemsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [RelatedEntitiesComponent],
declarations: [RelatedItemsComponent],
providers: [],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(RelatedEntitiesComponent, {
}).overrideComponent(RelatedItemsComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(RelatedEntitiesComponent);
fixture = TestBed.createComponent(RelatedItemsComponent);
comp = fixture.componentInstance;
comp.entities = mockEntities;
comp.items = mockItems;
fixture.detectChanges();
}));
it(`should load ${mockEntities.length} entity-type-switcher components`, () => {
const fields = fixture.debugElement.queryAll(By.css('ds-entity-type-switcher'));
expect(fields.length).toBe(mockEntities.length);
it(`should load ${mockItems.length} item-type-switcher components`, () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
expect(fields.length).toBe(mockItems.length);
});
});

View File

@@ -1,32 +1,32 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { EntityType } from '../../../shared/entities/entity-type.model';
import { ItemType } from '../../../shared/item-relationships/item-type.model';
import { ResourceType } from '../../../shared/resource-type';
import { mapsTo } from '../../builders/build-decorators';
import { NormalizedObject } from '../normalized-object.model';
import { IDToUUIDSerializer } from '../../id-to-uuid-serializer';
/**
* Normalized model class for a DSpace EntityType
* Normalized model class for a DSpace ItemType
*/
@mapsTo(EntityType)
@mapsTo(ItemType)
@inheritSerialization(NormalizedObject)
export class NormalizedEntityType extends NormalizedObject {
export class NormalizedItemType extends NormalizedObject {
/**
* The label that describes the ResourceType of the Entity
* The label that describes the ResourceType of the Item
*/
@autoserialize
label: string;
/**
* The identifier of this EntityType
* The identifier of this ItemType
*/
@autoserialize
id: string;
/**
* The universally unique identifier of this EntityType
* The universally unique identifier of this ItemType
*/
@autoserializeAs(new IDToUUIDSerializer(ResourceType.EntityType), 'id')
@autoserializeAs(new IDToUUIDSerializer(ResourceType.ItemType), 'id')
uuid: string;
}

View File

@@ -1,5 +1,5 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { RelationshipType } from '../../../shared/entities/relationship-type.model';
import { RelationshipType } from '../../../shared/item-relationships/relationship-type.model';
import { ResourceType } from '../../../shared/resource-type';
import { mapsTo, relationship } from '../../builders/build-decorators';
import { NormalizedDSpaceObject } from '../normalized-dspace-object.model';
@@ -56,17 +56,17 @@ export class NormalizedRelationshipType extends NormalizedObject {
rightMinCardinality: number;
/**
* The type of Entity found to the left of this RelationshipType
* The type of Item found to the left of this RelationshipType
*/
@autoserialize
@relationship(ResourceType.EntityType, false)
@relationship(ResourceType.ItemType, false)
leftType: string;
/**
* The type of Entity found to the right of this RelationshipType
* The type of Item found to the right of this RelationshipType
*/
@autoserialize
@relationship(ResourceType.EntityType, false)
@relationship(ResourceType.ItemType, false)
rightType: string;
/**

View File

@@ -1,5 +1,5 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { Relationship } from '../../../shared/entities/relationship.model';
import { Relationship } from '../../../shared/item-relationships/relationship.model';
import { ResourceType } from '../../../shared/resource-type';
import { mapsTo, relationship } from '../../builders/build-decorators';
import { NormalizedObject } from '../normalized-object.model';
@@ -19,25 +19,25 @@ export class NormalizedRelationship extends NormalizedObject {
id: string;
/**
* The identifier of the Entity to the left side of this Relationship
* The identifier of the Item to the left side of this Relationship
*/
@autoserialize
leftId: string;
/**
* The identifier of the Entity to the right side of this Relationship
* The identifier of the Item to the right side of this Relationship
*/
@autoserialize
rightId: string;
/**
* The place of the Entity to the left side of this Relationship
* The place of the Item to the left side of this Relationship
*/
@autoserialize
leftPlace: number;
/**
* The place of the Entity to the right side of this Relationship
* The place of the Item to the right side of this Relationship
*/
@autoserialize
rightPlace: number;

View File

@@ -1,6 +1,6 @@
import { NormalizedEntityType } from './entities/normalized-entity-type.model';
import { NormalizedRelationshipType } from './entities/normalized-relationship-type.model';
import { NormalizedRelationship } from './entities/normalized-relationship.model';
import { NormalizedItemType } from './items/normalized-item-type.model';
import { NormalizedRelationshipType } from './items/normalized-relationship-type.model';
import { NormalizedRelationship } from './items/normalized-relationship.model';
import { NormalizedBitstream } from './normalized-bitstream.model';
import { NormalizedBundle } from './normalized-bundle.model';
import { NormalizedItem } from './normalized-item.model';
@@ -44,8 +44,8 @@ export class NormalizedObjectFactory {
case ResourceType.RelationshipType: {
return NormalizedRelationshipType
}
case ResourceType.EntityType: {
return NormalizedEntityType
case ResourceType.ItemType: {
return NormalizedItemType
}
case ResourceType.EPerson: {
return NormalizedEPerson

View File

@@ -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;
}

View File

@@ -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;
});
}
}

View File

@@ -2,11 +2,11 @@ import { CacheableObject } from '../../cache/object-cache.reducer';
import { ResourceType } from '../resource-type';
/**
* Describes a type of Entity
* Describes a type of Item
*/
export class EntityType implements CacheableObject {
export class ItemType implements CacheableObject {
/**
* The identifier of this EntityType
* The identifier of this ItemType
*/
id: string;
@@ -21,7 +21,7 @@ export class EntityType implements CacheableObject {
type: ResourceType;
/**
* The universally unique identifier of this EntityType
* The universally unique identifier of this ItemType
*/
uuid: string;
}

View File

@@ -2,10 +2,10 @@ import { Observable } from 'rxjs';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { RemoteData } from '../../data/remote-data';
import { ResourceType } from '../resource-type';
import { EntityType } from './entity-type.model';
import { ItemType } from './item-type.model';
/**
* Describes a type of Relationship between multiple possible Entities
* Describes a type of Relationship between multiple possible Items
*/
export class RelationshipType implements CacheableObject {
/**
@@ -64,12 +64,12 @@ export class RelationshipType implements CacheableObject {
rightMinCardinality: number;
/**
* The type of Entity found to the left of this RelationshipType
* The type of Item found to the left of this RelationshipType
*/
leftType: Observable<RemoteData<EntityType>>;
leftType: Observable<RemoteData<ItemType>>;
/**
* The type of Entity found to the right of this RelationshipType
* The type of Item found to the right of this RelationshipType
*/
rightType: Observable<RemoteData<EntityType>>;
rightType: Observable<RemoteData<ItemType>>;
}

View File

@@ -5,7 +5,7 @@ import { ResourceType } from '../resource-type';
import { RelationshipType } from './relationship-type.model';
/**
* Describes a Relationship between two Entities
* Describes a Relationship between two Items
*/
export class Relationship implements CacheableObject {
/**
@@ -29,22 +29,22 @@ export class Relationship implements CacheableObject {
id: string;
/**
* The identifier of the Entity to the left side of this Relationship
* The identifier of the Item to the left side of this Relationship
*/
leftId: string;
/**
* The identifier of the Entity to the right side of this Relationship
* The identifier of the Item to the right side of this Relationship
*/
rightId: string;
/**
* The place of the Entity to the left side of this Relationship
* The place of the Item to the left side of this Relationship
*/
leftPlace: number;
/**
* The place of the Entity to the right side of this Relationship
* The place of the Item to the right side of this Relationship
*/
rightPlace: number;

View File

@@ -7,7 +7,7 @@ import { RemoteData } from '../data/remote-data';
import { Bitstream } from './bitstream.model';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { PaginatedList } from '../data/paginated-list';
import { Relationship } from './entities/relationship.model';
import { Relationship } from './item-relationships/relationship.model';
export class Item extends DSpaceObject {

View File

@@ -0,0 +1,36 @@
import { MetadataRepresentationType } from '../metadata-representation.model';
import { ItemMetadataRepresentation, ItemTypeToValue } 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(ItemTypeToValue).map((key: string) => {
return Object.assign(new Metadatum(), {
key: ItemTypeToValue[key],
value: `${valuePrefix}${ItemTypeToValue[key]}`
});
});
for (const itemType of Object.keys(ItemTypeToValue)) {
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 getValue', () => {
expect(itemMetadataRepresentation.getValue()).toEqual(`${valuePrefix}${ItemTypeToValue[itemType]}`);
});
it('should return the correct item type', () => {
expect(itemMetadataRepresentation.itemType).toEqual(itemType);
});
});
}
});

View File

@@ -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 value
*/
export const ItemTypeToValue = {
Default: 'dc.title',
Person: 'dc.contributor.author'
};
/**
* This class determines which fields to use when rendering an Item as a metadata value.
*/
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
*/
getValue(): string {
let metadata;
if (hasValue(ItemTypeToValue[this.itemType])) {
metadata = ItemTypeToValue[this.itemType];
} else {
metadata = ItemTypeToValue.Default;
}
return this.findMetadata(metadata);
}
}

View File

@@ -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 value to be displayed
*/
getValue(): string
}

View File

@@ -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.getValue()).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 getValue', () => {
expect(metadatumRepresentation.getValue()).toEqual(authorityMetadatum.value);
});
it('should return the correct item type', () => {
expect(metadatumRepresentation.itemType).toEqual(itemType);
});
});
});

View File

@@ -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
*/
getValue(): string {
return this.value;
}
}

View File

@@ -0,0 +1,67 @@
import { Metadatum } from './metadatum.model';
describe('Metadatum', () => {
let metadatum: Metadatum ;
beforeEach(() => {
metadatum = new Metadatum();
});
describe('isVirtual', () => {
describe('when the metadatum has no authority key', () => {
beforeEach(() => {
metadatum.authority = undefined;
});
it('should return false', () => {
expect(metadatum.isVirtual).toBe(false);
});
});
describe('when the metadatum has an authority key', () => {
describe('but it doesn\'t start with the virtual prefix', () => {
beforeEach(() => {
metadatum.authority = 'value';
});
it('should return false', () => {
expect(metadatum.isVirtual).toBe(false);
});
});
describe('and it starts with the virtual prefix', () => {
beforeEach(() => {
metadatum.authority = 'virtual::value';
});
it('should return true', () => {
expect(metadatum.isVirtual).toBe(true);
});
});
});
});
describe('virtualValue', () => {
describe('when the metadatum isn\'t virtual', () => {
beforeEach(() => {
metadatum.authority = 'value';
});
it('should return undefined', () => {
expect(metadatum.virtualValue).toBeUndefined();
});
});
describe('when the metadatum is virtual', () => {
beforeEach(() => {
metadatum.authority = 'virtual::value';
});
it('should return everything in the authority key after virtual::', () => {
expect(metadatum.virtualValue).toBe('value');
});
});
});
});

View File

@@ -1,4 +1,7 @@
import { autoserialize } from 'cerialize';
import { hasValue } from '../../shared/empty.util';
const VIRTUAL_METADATA_PREFIX = 'virtual::';
export class Metadatum {
@@ -20,4 +23,42 @@ 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;
/**
* The authority confidence value
*/
@autoserialize
confidence: number;
/**
* Returns true if this Metadatum's authority key starts with 'virtual::'
*/
get isVirtual(): boolean {
return hasValue(this.authority) && this.authority.startsWith(VIRTUAL_METADATA_PREFIX);
}
/**
* If this is a virtual Metadatum, it returns everything in the authority key after 'virtual::'.
* Returns undefined otherwise.
*/
get virtualValue(): string {
if (this.isVirtual) {
return this.authority.substring(this.authority.indexOf(VIRTUAL_METADATA_PREFIX) + VIRTUAL_METADATA_PREFIX.length);
} else {
return undefined;
}
}
}

View File

@@ -11,5 +11,5 @@ export enum ResourceType {
ResourcePolicy = 'resourcePolicy',
Relationship = 'relationship',
RelationshipType = 'relationshiptype',
EntityType = 'entitytype',
ItemType = 'entitytype',
}

View File

@@ -1,36 +0,0 @@
import { hasNoValue, hasValue } from '../empty.util';
import { ElementViewMode } from '../view-mode';
export const DEFAULT_ENTITY_TYPE = 'Default';
const map = new Map();
/**
* Decorator used for rendering simple item pages for an Entity by type and viewMode
* @param type
* @param viewMode
*/
export function rendersEntityType(type: string, viewMode: ElementViewMode) {
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}"`);
}
map.get(viewMode).set(type, component);
};
}
/**
* Get the component used for rendering an entity by type and viewMode
* @param type
* @param viewMode
*/
export function getComponentByEntityType(type: string, viewMode: ElementViewMode) {
let component = map.get(viewMode).get(type);
if (hasNoValue(component)) {
component = map.get(viewMode).get(DEFAULT_ENTITY_TYPE);
}
return component;
}

View File

@@ -1,60 +0,0 @@
import { EntityTypeSwitcherComponent } from './entity-type-switcher.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { PageInfo } from '../../../core/shared/page-info.model';
import { Item } from '../../../core/shared/item.model';
import { PaginatedList } from '../../../core/data/paginated-list';
import { RemoteData } from '../../../core/data/remote-data';
import * as decorator from '../entity-type-decorator';
import { getComponentByEntityType } from '../entity-type-decorator';
import { ElementViewMode } from '../../view-mode';
import createSpy = jasmine.createSpy;
const relationType = 'type';
const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'test item'
},
{
key: 'relationship.type',
language: 'en_US',
value: relationType
}]
});
const viewMode = ElementViewMode.Full;
describe('EntityTypeSwitcherComponent', () => {
let comp: EntityTypeSwitcherComponent;
let fixture: ComponentFixture<EntityTypeSwitcherComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EntityTypeSwitcherComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
}).compileComponents(); // compile template and css
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(EntityTypeSwitcherComponent);
comp = fixture.componentInstance;
comp.object = mockItem;
comp.viewMode = viewMode;
spyOnProperty(decorator, 'getComponentByEntityType').and.returnValue(createSpy('getComponentByEntityType'))
}));
describe('when calling getComponent', () => {
beforeEach(() => {
comp.getComponent();
});
it('should call getComponentByEntityType with parameters type and viewMode', () => {
expect(decorator.getComponentByEntityType).toHaveBeenCalledWith(relationType, viewMode);
});
});
});

View File

@@ -0,0 +1,60 @@
import { hasNoValue, hasValue } from '../empty.util';
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 (and optionally a representationType)
* @param type
* @param viewMode
* @param representationType
*/
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 (hasNoValue(map.get(viewMode).get(type))) {
map.get(viewMode).set(type, new Map());
}
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 (and optionally a representationType)
* @param type
* @param viewMode
* @param representationType
*/
export function getComponentByItemType(type: string, viewMode: string, representationType?: MetadataRepresentationType) {
if (hasNoValue(representationType)) {
representationType = NO_REPRESENTATION_TYPE;
}
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;
}

View File

@@ -0,0 +1,89 @@
import { ItemTypeSwitcherComponent } from './item-type-switcher.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { PageInfo } from '../../../core/shared/page-info.model';
import { Item } from '../../../core/shared/item.model';
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 { 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(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'test item'
},
{
key: 'relationship.type',
language: 'en_US',
value: relationType
}]
});
const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(relationType), mockItem);
let viewMode = VIEW_MODE_FULL;
describe('ItemTypeSwitcherComponent', () => {
let comp: ItemTypeSwitcherComponent;
let fixture: ComponentFixture<ItemTypeSwitcherComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemTypeSwitcherComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
}).compileComponents(); // compile template and css
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemTypeSwitcherComponent);
comp = fixture.componentInstance;
comp.object = mockItem;
comp.viewMode = viewMode;
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();
});
it('should call getComponentByItemType with parameters type and viewMode', () => {
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);
});
});
});
});

View File

@@ -3,29 +3,29 @@ import { SearchResult } from '../../../+search-page/search-result.model';
import { Item } from '../../../core/shared/item.model';
import { hasValue } from '../../empty.util';
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
import { getComponentByEntityType } from '../entity-type-decorator';
import { ElementViewMode } from '../../view-mode';
import { getComponentByItemType } from '../item-type-decorator';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
export const ITEM: InjectionToken<string> = new InjectionToken<string>('item');
@Component({
selector: 'ds-entity-type-switcher',
styleUrls: ['./entity-type-switcher.component.scss'],
templateUrl: './entity-type-switcher.component.html'
selector: 'ds-item-type-switcher',
styleUrls: ['./item-type-switcher.component.scss'],
templateUrl: './item-type-switcher.component.html'
})
/**
* Component for determining what component to use depending on the item's relationship type (relationship.type)
*/
export class EntityTypeSwitcherComponent implements OnInit {
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 EntityTypeSwitcherComponent 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;
@@ -57,6 +62,6 @@ export class EntityTypeSwitcherComponent implements OnInit {
}
const type = item.findMetadata('relationship.type');
return getComponentByEntityType(type, this.viewMode);
return getComponentByItemType(type, this.viewMode);
}
}

View File

@@ -1 +0,0 @@
<ds-entity-type-switcher [object]="object" [viewMode]="ElementViewMode.SetElement"></ds-entity-type-switcher>

View File

@@ -1,22 +0,0 @@
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';
@Component({
selector: 'ds-entity-list-element',
styleUrls: ['./entity-list-element.component.scss'],
templateUrl: './entity-list-element.component.html'
})
/**
* The component used to list entities depending on type
* Uses entity-type-switcher to determine which components to use for displaying the list
*/
@renderElementsFor(Item, SetViewMode.List)
export class EntityListElementComponent extends AbstractListableElementComponent<Item> {
ElementViewMode = viewMode.ElementViewMode;
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('JournalIssue', ElementViewMode.SetElement)
@Component({
selector: 'ds-journal-issue-list-element',
styleUrls: ['./journal-issue-list-element.component.scss'],
templateUrl: './journal-issue-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Journal Issue
*/
export class JournalIssueListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('JournalVolume', ElementViewMode.SetElement)
@Component({
selector: 'ds-journal-volume-list-element',
styleUrls: ['./journal-volume-list-element.component.scss'],
templateUrl: './journal-volume-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Journal Volume
*/
export class JournalVolumeListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('Journal', ElementViewMode.SetElement)
@Component({
selector: 'ds-journal-list-element',
styleUrls: ['./journal-list-element.component.scss'],
templateUrl: './journal-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Journal
*/
export class JournalListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('OrgUnit', ElementViewMode.SetElement)
@Component({
selector: 'ds-orgunit-list-element',
styleUrls: ['./orgunit-list-element.component.scss'],
templateUrl: './orgunit-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Organisation Unit
*/
export class OrgUnitListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('Person', ElementViewMode.SetElement)
@Component({
selector: 'ds-person-list-element',
styleUrls: ['./person-list-element.component.scss'],
templateUrl: './person-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Person
*/
export class PersonListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -1,16 +0,0 @@
import { Component } from '@angular/core';
import { rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('Project', ElementViewMode.SetElement)
@Component({
selector: 'ds-project-list-element',
styleUrls: ['./project-list-element.component.scss'],
templateUrl: './project-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Project
*/
export class ProjectListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -1,17 +0,0 @@
import { Component } from '@angular/core';
import { DEFAULT_ENTITY_TYPE, rendersEntityType } from '../../../../entities/entity-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { EntitySearchResultComponent } from '../entity-search-result-component';
@rendersEntityType('Publication', ElementViewMode.SetElement)
@rendersEntityType(DEFAULT_ENTITY_TYPE, ElementViewMode.SetElement)
@Component({
selector: 'ds-publication-list-element',
styleUrls: ['./publication-list-element.component.scss'],
templateUrl: './publication-list-element.component.html'
})
/**
* The component for displaying a list element for an item with entity type Publication
*/
export class PublicationListElementComponent extends EntitySearchResultComponent {
}

View File

@@ -0,0 +1 @@
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>

View File

@@ -1,13 +1,12 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { EntityListElementComponent } from './entity-list-element.component';
import { ItemListElementComponent } from './item-list-element.component';
import { Item } from '../../../core/shared/item.model';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { PageInfo } from '../../../core/shared/page-info.model';
import { By } from '@angular/platform-browser';
import { createRelationshipsObservable } from '../../../+item-page/simple/entity-types/shared/entity.component.spec';
import { createRelationshipsObservable } from '../../../+item-page/simple/item-types/shared/item.component.spec';
import { of as observableOf } from 'rxjs';
const mockItem: Item = Object.assign(new Item(), {
@@ -16,32 +15,32 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
describe('EntityListElementComponent', () => {
let comp: EntityListElementComponent;
let fixture: ComponentFixture<EntityListElementComponent>;
describe('ItemListElementComponent', () => {
let comp: ItemListElementComponent;
let fixture: ComponentFixture<ItemListElementComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [EntityListElementComponent],
declarations: [ItemListElementComponent],
providers: [
{ provide: 'objectElementProvider', useValue: mockItem }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(EntityListElementComponent, {
}).overrideComponent(ItemListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(EntityListElementComponent);
fixture = TestBed.createComponent(ItemListElementComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
}));
it('should call an entity-type-switcher component and pass the item', () => {
const entityTypeSwitcher = fixture.debugElement.query(By.css('ds-entity-type-switcher')).componentInstance;
expect(entityTypeSwitcher.object).toBe(mockItem);
it('should call an item-type-switcher component and pass the item', () => {
const itemTypeSwitcher = fixture.debugElement.query(By.css('ds-item-type-switcher')).componentInstance;
expect(itemTypeSwitcher.object).toBe(mockItem);
});
});

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
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',
styleUrls: ['./item-list-element.component.scss'],
templateUrl: './item-list-element.component.html'
})
/**
* The component used to list items depending on type
* Uses item-type-switcher to determine which components to use for displaying the list
*/
@renderElementsFor(Item, SetViewMode.List)
export class ItemListElementComponent extends AbstractListableElementComponent<Item> {
viewMode = VIEW_MODE_ELEMENT;
}

View File

@@ -1,11 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { ITEM } from '../../../../entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
import { JournalIssueListElementComponent } from './journal-issue-list-element.component';
import { of as observableOf } from 'rxjs';

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
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', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-journal-issue-list-element',
styleUrls: ['./journal-issue-list-element.component.scss'],
templateUrl: './journal-issue-list-element.component.html'
})
/**
* The component for displaying a list element for an item of the type Journal Issue
*/
export class JournalIssueListElementComponent extends TypedItemSearchResultListElementComponent {
}

View File

@@ -1,11 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { ITEM } from '../../../../entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
import { JournalVolumeListElementComponent } from './journal-volume-list-element.component';
import { of as observableOf } from 'rxjs';

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
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', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-journal-volume-list-element',
styleUrls: ['./journal-volume-list-element.component.scss'],
templateUrl: './journal-volume-list-element.component.html'
})
/**
* The component for displaying a list element for an item of the type Journal Volume
*/
export class JournalVolumeListElementComponent extends TypedItemSearchResultListElementComponent {
}

View File

@@ -1,11 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { ITEM } from '../../../../entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
import { JournalListElementComponent } from './journal-list-element.component';
import { of as observableOf } from 'rxjs';

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
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', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-journal-list-element',
styleUrls: ['./journal-list-element.component.scss'],
templateUrl: './journal-list-element.component.html'
})
/**
* The component for displaying a list element for an item of the type Journal
*/
export class JournalListElementComponent extends TypedItemSearchResultListElementComponent {
}

View File

@@ -1,11 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs';
import { Item } from '../../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { ITEM } from '../../../../entities/switcher/entity-type-switcher.component';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
import { OrgUnitListElementComponent } from './orgunit-list-element.component';
import { of as observableOf } from 'rxjs';

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
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', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-orgunit-list-element',
styleUrls: ['./orgunit-list-element.component.scss'],
templateUrl: './orgunit-list-element.component.html'
})
/**
* The component for displaying a list element for an item of the type Organisation Unit
*/
export class OrgUnitListElementComponent extends TypedItemSearchResultListElementComponent {
}

Some files were not shown because too many files have changed in this diff Show More