Merge remote-tracking branch 'remotes/origin/master' into submission-miscellaneous-fixes

# Conflicts:
#	src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
This commit is contained in:
Giuseppe Digilio
2019-09-05 14:54:27 +02:00
99 changed files with 3059 additions and 435 deletions

View File

@@ -2,16 +2,47 @@
"404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
"404.link.home-page": "Take me to the home page",
"404.page-not-found": "page not found",
"admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.",
"admin.registries.bitstream-formats.create.failure.head": "Failure",
"admin.registries.bitstream-formats.create.head": "Create Bitstream format",
"admin.registries.bitstream-formats.create.new": "Add a new bitstream format",
"admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.",
"admin.registries.bitstream-formats.create.success.head": "Success",
"admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)",
"admin.registries.bitstream-formats.delete.failure.head": "Failure",
"admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)",
"admin.registries.bitstream-formats.delete.success.head": "Success",
"admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.",
"admin.registries.bitstream-formats.formats.no-items": "No bitstream formats to show.",
"admin.registries.bitstream-formats.formats.table.internal": "internal",
"admin.registries.bitstream-formats.formats.table.mimetype": "MIME Type",
"admin.registries.bitstream-formats.formats.table.name": "Name",
"admin.registries.bitstream-formats.formats.table.supportLevel.0": "Unknown",
"admin.registries.bitstream-formats.formats.table.supportLevel.1": "Known",
"admin.registries.bitstream-formats.formats.table.supportLevel.2": "Support",
"admin.registries.bitstream-formats.formats.table.supportLevel.head": "Support Level",
"admin.registries.bitstream-formats.edit.description.hint": "",
"admin.registries.bitstream-formats.edit.description.label": "Description",
"admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.",
"admin.registries.bitstream-formats.edit.extensions.label": "File extensions",
"admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot",
"admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.",
"admin.registries.bitstream-formats.edit.failure.head": "Failure",
"admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}",
"admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.",
"admin.registries.bitstream-formats.edit.internal.label": "Internal",
"admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.",
"admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type",
"admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)",
"admin.registries.bitstream-formats.edit.shortDescription.label": "Name",
"admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.",
"admin.registries.bitstream-formats.edit.success.head": "Success",
"admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.",
"admin.registries.bitstream-formats.edit.supportLevel.label": "Support level",
"admin.registries.bitstream-formats.head": "Bitstream Format Registry",
"admin.registries.bitstream-formats.no-items": "No bitstream formats to show.",
"admin.registries.bitstream-formats.table.delete": "Delete selected",
"admin.registries.bitstream-formats.table.deselect-all": "Deselect all",
"admin.registries.bitstream-formats.table.internal": "internal",
"admin.registries.bitstream-formats.table.mimetype": "MIME Type",
"admin.registries.bitstream-formats.table.name": "Name",
"admin.registries.bitstream-formats.table.return": "Return",
"admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known",
"admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported",
"admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown",
"admin.registries.bitstream-formats.table.supportLevel.head": "Support Level",
"admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry",
"admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
"admin.registries.metadata.form.create": "Create metadata schema",
@@ -101,6 +132,7 @@
"collection.form.tableofcontents": "News (HTML)",
"collection.form.title": "Name",
"collection.page.browse.recent.head": "Recent Submissions",
"collection.page.browse.recent.empty": "No items to show",
"collection.page.license": "License",
"collection.page.news": "News",
"community.create.head": "Create a Community",

View File

@@ -2,14 +2,29 @@ import { MetadataRegistryComponent } from './metadata-registry/metadata-registry
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
import { URLCombiner } from '../../core/url-combiner/url-combiner';
import { getRegistriesModulePath } from '../admin-routing.module';
const BITSTREAMFORMATS_MODULE_PATH = 'bitstream-formats';
export function getBitstreamFormatsModulePath() {
return new URLCombiner(getRegistriesModulePath(), BITSTREAMFORMATS_MODULE_PATH).toString();
}
@NgModule({
imports: [
RouterModule.forChild([
{ path: 'metadata', component: MetadataRegistryComponent, data: { title: 'admin.registries.metadata.title' } },
{ path: 'metadata/:schemaName', component: MetadataSchemaComponent, data: { title: 'admin.registries.schema.title' } },
{ path: 'bitstream-formats', component: BitstreamFormatsComponent, data: { title: 'admin.registries.bitstream-formats.title' } },
{path: 'metadata', component: MetadataRegistryComponent, data: {title: 'admin.registries.metadata.title'}},
{
path: 'metadata/:schemaName',
component: MetadataSchemaComponent,
data: {title: 'admin.registries.schema.title'}
},
{
path: BITSTREAMFORMATS_MODULE_PATH,
loadChildren: './bitstream-formats/bitstream-formats.module#BitstreamFormatsModule',
data: {title: 'admin.registries.bitstream-formats.title'}
},
])
]
})

View File

@@ -5,10 +5,10 @@ import { CommonModule } from '@angular/common';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
import { SharedModule } from '../../shared/shared.module';
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/metadata-field-form.component';
import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
@NgModule({
imports: [
@@ -16,12 +16,12 @@ import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/
SharedModule,
RouterModule,
TranslateModule,
BitstreamFormatsModule,
AdminRegistriesRoutingModule
],
declarations: [
MetadataRegistryComponent,
MetadataSchemaComponent,
BitstreamFormatsComponent,
MetadataSchemaFormComponent,
MetadataFieldFormComponent
],

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2 id="sub-header"
class="border-bottom mb-2">{{ 'admin.registries.bitstream-formats.create.new' | translate }}</h2>
<ds-bitstream-format-form (updatedFormat)="createBitstreamFormat($event)"></ds-bitstream-format-form>
</div>
</div>
</div>

View File

@@ -0,0 +1,106 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { Router } from '@angular/router';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RouterStub } from '../../../../shared/testing/router-stub';
import { of as observableOf } from 'rxjs';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { ResourceType } from '../../../../core/shared/resource-type';
import { AddBitstreamFormatComponent } from './add-bitstream-format.component';
describe('AddBitstreamFormatComponent', () => {
let comp: AddBitstreamFormatComponent;
let fixture: ComponentFixture<AddBitstreamFormatComponent>;
const bitstreamFormat = new BitstreamFormat();
bitstreamFormat.uuid = 'test-uuid-1';
bitstreamFormat.id = 'test-uuid-1';
bitstreamFormat.shortDescription = 'Unknown';
bitstreamFormat.description = 'Unknown data format';
bitstreamFormat.mimetype = 'application/octet-stream';
bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat.internal = false;
bitstreamFormat.extensions = null;
let router;
let notificationService: NotificationsServiceStub;
let bitstreamFormatDataService: BitstreamFormatDataService;
const initAsync = () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')),
clearBitStreamFormatRequests: observableOf(null)
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [AddBitstreamFormatComponent],
providers: [
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
};
const initBeforeEach = () => {
fixture = TestBed.createComponent(AddBitstreamFormatComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
};
describe('createBitstreamFormat success', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.createBitstreamFormat(bitstreamFormat);
expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.success).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
});
});
describe('createBitstreamFormat error', () => {
beforeEach(async(() => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')),
clearBitStreamFormatRequests: observableOf(null)
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [AddBitstreamFormatComponent],
providers: [
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.createBitstreamFormat(bitstreamFormat);
expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.error).toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,49 @@
import { take } from 'rxjs/operators';
import { Router } from '@angular/router';
import { Component } from '@angular/core';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
import { TranslateService } from '@ngx-translate/core';
/**
* This component renders the page to create a new bitstream format.
*/
@Component({
selector: 'ds-add-bitstream-format',
templateUrl: './add-bitstream-format.component.html',
})
export class AddBitstreamFormatComponent {
constructor(
private router: Router,
private notificationService: NotificationsService,
private translateService: TranslateService,
private bitstreamFormatDataService: BitstreamFormatDataService,
) {
}
/**
* Creates a new bitstream format based on the provided bitstream format emitted by the form.
* When successful, a success notification will be shown and the user will be navigated back to the overview page.
* When failed, an error notification will be shown.
* @param bitstreamFormat
*/
createBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.createBitstreamFormat(bitstreamFormat).pipe(take(1)
).subscribe((response: RestResponse) => {
if (response.isSuccessful) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.create.success.head'),
this.translateService.get('admin.registries.bitstream-formats.create.success.content'));
this.router.navigate([getBitstreamFormatsModulePath()]);
this.bitstreamFormatDataService.clearBitStreamFormatRequests().subscribe();
} else {
this.notificationService.error(this.translateService.get('admin.registries.bitstream-formats.create.failure.head'),
this.translateService.get('admin.registries.bitstream-formats.create.failure.content'));
}
}
);
}
}

View File

@@ -0,0 +1,64 @@
import { Action } from '@ngrx/store';
import { type } from '../../../shared/ngrx/type';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
/**
* For each action type in an action group, make a simple
* enum object for all of this group's action types.
*
* The 'type' utility function coerces strings into string
* literal types and runs a simple check to guarantee all
* action types in the application are unique.
*/
export const BitstreamFormatsRegistryActionTypes = {
SELECT_FORMAT: type('dspace/bitstream-formats-registry/SELECT_FORMAT'),
DESELECT_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_FORMAT'),
DESELECT_ALL_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_ALL_FORMAT')
};
/* tslint:disable:max-classes-per-file */
/**
* Used to select a single bitstream format in the bitstream format registry
*/
export class BitstreamFormatsRegistrySelectAction implements Action {
type = BitstreamFormatsRegistryActionTypes.SELECT_FORMAT;
bitstreamFormat: BitstreamFormat;
constructor(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormat = bitstreamFormat;
}
}
/**
* Used to deselect a single bitstream format in the bitstream format registry
*/
export class BitstreamFormatsRegistryDeselectAction implements Action {
type = BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT;
bitstreamFormat: BitstreamFormat;
constructor(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormat = bitstreamFormat;
}
}
/**
* Used to deselect all bitstream formats in the bitstream format registry
*/
export class BitstreamFormatsRegistryDeselectAllAction implements Action {
type = BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT;
}
/* tslint:enable:max-classes-per-file */
/**
* Export a type alias of all actions in this action group
* so that reducers can easily compose action types
* These are all the actions to perform on the bitstream format registry state
*/
export type BitstreamFormatsRegistryAction
= BitstreamFormatsRegistrySelectAction
| BitstreamFormatsRegistryDeselectAction
| BitstreamFormatsRegistryDeselectAllAction

View File

@@ -0,0 +1,83 @@
import { Action } from '@ngrx/store';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './bitstream-format.reducers';
import {
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistryDeselectAllAction,
BitstreamFormatsRegistrySelectAction
} from './bitstream-format.actions';
const bitstreamFormat1: BitstreamFormat = new BitstreamFormat();
bitstreamFormat1.id = 'test-uuid-1';
bitstreamFormat1.shortDescription = 'test-short-1';
const bitstreamFormat2: BitstreamFormat = new BitstreamFormat();
bitstreamFormat2.id = 'test-uuid-2';
bitstreamFormat2.shortDescription = 'test-short-2';
const initialState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: []
};
const bitstream1SelectedState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: [bitstreamFormat1]
};
const bitstream1and2SelectedState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: [bitstreamFormat1, bitstreamFormat2]
};
describe('BitstreamFormatReducer', () => {
describe('BitstreamFormatsRegistryActionTypes.SELECT_FORMAT', () => {
it('should add the format to the list of selected formats when initial list is empty', () => {
const state = initialState;
const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat1);
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(bitstream1SelectedState);
});
it('should add the format to the list of selected formats when formats are already present', () => {
const state = bitstream1SelectedState;
const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat2);
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(bitstream1and2SelectedState);
});
});
describe('BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT', () => {
it('should deselect a format', () => {
const state = bitstream1and2SelectedState;
const action = new BitstreamFormatsRegistryDeselectAction(bitstreamFormat2);
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(bitstream1SelectedState);
});
});
describe('BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT', () => {
it('should deselect all formats', () => {
const state = bitstream1and2SelectedState;
const action = new BitstreamFormatsRegistryDeselectAllAction();
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(initialState);
});
});
describe('Invalid action', () => {
it('should return the current state', () => {
const state = initialState;
const action = new NullAction();
const newState = bitstreamFormatReducer(state, action);
expect(newState).toEqual(state);
});
});
});
class NullAction implements Action {
type = null;
constructor() {
// empty constructor
}
}

View File

@@ -0,0 +1,55 @@
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import {
BitstreamFormatsRegistryAction,
BitstreamFormatsRegistryActionTypes,
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistrySelectAction
} from './bitstream-format.actions';
/**
* The bitstream format registry state.
* @interface BitstreamFormatRegistryState
*/
export interface BitstreamFormatRegistryState {
selectedBitstreamFormats: BitstreamFormat[];
}
/**
* The initial state.
*/
const initialState: BitstreamFormatRegistryState = {
selectedBitstreamFormats: [],
};
/**
* Reducer that handles BitstreamFormatsRegistryActions to modify the bitstream format registry state
* @param state The current BitstreamFormatRegistryState
* @param action The BitstreamFormatsRegistryAction to perform on the state
*/
export function bitstreamFormatReducer(state = initialState, action: BitstreamFormatsRegistryAction): BitstreamFormatRegistryState {
switch (action.type) {
case BitstreamFormatsRegistryActionTypes.SELECT_FORMAT: {
return Object.assign({}, state, {
selectedBitstreamFormats: [...state.selectedBitstreamFormats, (action as BitstreamFormatsRegistrySelectAction).bitstreamFormat]
});
}
case BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT: {
return Object.assign({}, state, {
selectedBitstreamFormats: state.selectedBitstreamFormats.filter(
(selectedBitstreamFormats) => selectedBitstreamFormats !== (action as BitstreamFormatsRegistryDeselectAction).bitstreamFormat
)
});
}
case BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT: {
return Object.assign({}, state, {
selectedBitstreamFormats: []
});
}
default:
return state;
}
}

View File

@@ -0,0 +1,37 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BitstreamFormatsResolver } from './bitstream-formats.resolver';
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
const BITSTREAMFORMAT_EDIT_PATH = ':id/edit';
const BITSTREAMFORMAT_ADD_PATH = 'add';
@NgModule({
imports: [
RouterModule.forChild([
{
path: '',
component: BitstreamFormatsComponent
},
{
path: BITSTREAMFORMAT_ADD_PATH,
component: AddBitstreamFormatComponent,
},
{
path: BITSTREAMFORMAT_EDIT_PATH,
component: EditBitstreamFormatComponent,
resolve: {
bitstreamFormat: BitstreamFormatsResolver
}
},
])
],
providers: [
BitstreamFormatsResolver,
]
})
export class BitstreamFormatsRoutingModule {
}

View File

@@ -2,13 +2,15 @@
<div class="bitstream-formats row">
<div class="col-12">
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.bitstream-formats.head' | translate}}</h2>
<h2 id="header" class="border-bottom pb-2 ">{{'admin.registries.bitstream-formats.head' | translate}}</h2>
<p id="description">{{'admin.registries.bitstream-formats.description' | translate}}</p>
<p id="create-new" class="mb-2"><a [routerLink]="'add'" class="btn btn-success">{{'admin.registries.bitstream-formats.create.new' | translate}}</a></p>
<p id="description" class="pb-2">{{'admin.registries.bitstream-formats.description' | translate}}</p>
<ds-pagination
*ngIf="(bitstreamFormats | async)?.payload?.totalElements > 0"
[paginationOptions]="config"
[paginationOptions]="pageConfig"
[pageInfoState]="(bitstreamFormats | async)?.payload"
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
[hideGear]="true"
@@ -18,25 +20,38 @@
<table id="formats" class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">{{'admin.registries.bitstream-formats.formats.table.name' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.formats.table.mimetype' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.formats.table.supportLevel.head' | translate}}</th>
<th scope="col"></th>
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
<td>{{bitstreamFormat.shortDescription}}</td>
<td>{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.formats.table.internal' | translate}})</span></td>
<td>{{'admin.registries.bitstream-formats.formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</td>
<td>
<label>
<input type="checkbox"
[checked]="isSelected(bitstreamFormat) | async"
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
>
</label>
</td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
</tr>
</tbody>
</table>
</div>
</ds-pagination>
<div *ngIf="(bitstreamFormats | async)?.payload?.totalElements == 0" class="alert alert-info" role="alert">
{{'admin.registries.bitstream-formats.formats.no-items' | translate}}
{{'admin.registries.bitstream-formats.no-items' | translate}}
</div>
<div>
<button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button>
<button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button>
</div>
</div>
</div>
</div>

View File

@@ -1,6 +1,5 @@
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RegistryService } from '../../../core/registry/registry.service';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
@@ -13,86 +12,278 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { HostWindowService } from '../../../shared/host-window.service';
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
describe('BitstreamFormatsComponent', () => {
let comp: BitstreamFormatsComponent;
let fixture: ComponentFixture<BitstreamFormatsComponent>;
let registryService: RegistryService;
const mockFormatsList = [
{
shortDescription: 'Unknown',
description: 'Unknown data format',
mimetype: 'application/octet-stream',
supportLevel: 0,
internal: false,
extensions: null
},
{
shortDescription: 'License',
description: 'Item-specific license agreed upon to submission',
mimetype: 'text/plain; charset=utf-8',
supportLevel: 1,
internal: true,
extensions: null
},
{
shortDescription: 'CC License',
description: 'Item-specific Creative Commons license agreed upon to submission',
mimetype: 'text/html; charset=utf-8',
supportLevel: 2,
internal: true,
extensions: null
},
{
shortDescription: 'Adobe PDF',
description: 'Adobe Portable Document Format',
mimetype: 'application/pdf',
supportLevel: 0,
internal: false,
extensions: null
}
];
const mockFormats = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFormatsList));
const registryServiceStub = {
getBitstreamFormats: () => mockFormats
};
let bitstreamFormatService;
let scheduler: TestScheduler;
let notificationsServiceStub;
const bitstreamFormat1 = new BitstreamFormat();
bitstreamFormat1.uuid = 'test-uuid-1';
bitstreamFormat1.id = 'test-uuid-1';
bitstreamFormat1.shortDescription = 'Unknown';
bitstreamFormat1.description = 'Unknown data format';
bitstreamFormat1.mimetype = 'application/octet-stream';
bitstreamFormat1.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat1.internal = false;
bitstreamFormat1.extensions = null;
const bitstreamFormat2 = new BitstreamFormat();
bitstreamFormat2.uuid = 'test-uuid-2';
bitstreamFormat2.id = 'test-uuid-2';
bitstreamFormat2.shortDescription = 'License';
bitstreamFormat2.description = 'Item-specific license agreed upon to submission';
bitstreamFormat2.mimetype = 'text/plain; charset=utf-8';
bitstreamFormat2.supportLevel = BitstreamFormatSupportLevel.Known;
bitstreamFormat2.internal = true;
bitstreamFormat2.extensions = null;
const bitstreamFormat3 = new BitstreamFormat();
bitstreamFormat3.uuid = 'test-uuid-3';
bitstreamFormat3.id = 'test-uuid-3';
bitstreamFormat3.shortDescription = 'CC License';
bitstreamFormat3.description = 'Item-specific Creative Commons license agreed upon to submission';
bitstreamFormat3.mimetype = 'text/html; charset=utf-8';
bitstreamFormat3.supportLevel = BitstreamFormatSupportLevel.Supported;
bitstreamFormat3.internal = true;
bitstreamFormat3.extensions = null;
const bitstreamFormat4 = new BitstreamFormat();
bitstreamFormat4.uuid = 'test-uuid-4';
bitstreamFormat4.id = 'test-uuid-4';
bitstreamFormat4.shortDescription = 'Adobe PDF';
bitstreamFormat4.description = 'Adobe Portable Document Format';
bitstreamFormat4.mimetype = 'application/pdf';
bitstreamFormat4.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat4.internal = false;
bitstreamFormat4.extensions = null;
const mockFormatsList: BitstreamFormat[] = [
bitstreamFormat1,
bitstreamFormat2,
bitstreamFormat3,
bitstreamFormat4
];
const mockFormatsRD = new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList));
const initAsync = () => {
notificationsServiceStub = new NotificationsServiceStub();
scheduler = getTestScheduler();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: observableOf(true),
clearBitStreamFormatRequests: observableOf('cleared')
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [
{ provide: RegistryService, useValue: registryServiceStub },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
{provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
{provide: NotificationsService, useValue: notificationsServiceStub}
]
}).compileComponents();
}));
};
beforeEach(() => {
const initBeforeEach = () => {
fixture = TestBed.createComponent(BitstreamFormatsComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
registryService = (comp as any).service;
};
describe('Bitstream format page content', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should contain four formats', () => {
const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
expect(tbody.children.length).toBe(4);
});
it('should contain the correct formats', () => {
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
expect(unknownName.textContent).toBe('Unknown');
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement;
expect(licenseName.textContent).toBe('License');
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement;
expect(ccLicenseName.textContent).toBe('CC License');
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement;
expect(adobeName.textContent).toBe('Adobe PDF');
});
});
it('should contain four formats', () => {
const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
expect(tbody.children.length).toBe(4);
describe('selectBitStreamFormat', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should select a bitstreamFormat if it was selected in the event', () => {
const event = {target: {checked: true}};
comp.selectBitStreamFormat(bitstreamFormat1, event);
expect(bitstreamFormatService.selectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
});
it('should deselect a bitstreamFormat if it is deselected in the event', () => {
const event = {target: {checked: false}};
comp.selectBitStreamFormat(bitstreamFormat1, event);
expect(bitstreamFormatService.deselectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
});
it('should be called when a user clicks a checkbox', () => {
spyOn(comp, 'selectBitStreamFormat');
const unknownFormat = fixture.debugElement.query(By.css('#formats tr:nth-child(1) input'));
const event = {target: {checked: true}};
unknownFormat.triggerEventHandler('change', event);
expect(comp.selectBitStreamFormat).toHaveBeenCalledWith(bitstreamFormat1, event);
});
});
it('should contain the correct formats', () => {
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(1)')).nativeElement;
expect(unknownName.textContent).toBe('Unknown');
describe('isSelected', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should return an observable of true if the provided bistream is in the list returned by the service', () => {
const result = comp.isSelected(bitstreamFormat1);
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(1)')).nativeElement;
expect(licenseName.textContent).toBe('License');
expect(result).toBeObservable(cold('b', {b: true}));
});
it('should return an observable of false if the provided bistream is not in the list returned by the service', () => {
const format = new BitstreamFormat();
format.uuid = 'new';
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(1)')).nativeElement;
expect(ccLicenseName.textContent).toBe('CC License');
const result = comp.isSelected(format);
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(1)')).nativeElement;
expect(adobeName.textContent).toBe('Adobe PDF');
expect(result).toBeObservable(cold('b', {b: false}));
});
});
describe('deselectAll', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should deselect all bitstreamFormats', () => {
comp.deselectAll();
expect(bitstreamFormatService.deselectAllBitstreamFormats).toHaveBeenCalled();
});
it('should be called when the deselect all button is clicked', () => {
spyOn(comp, 'deselectAll');
const deselectAllButton = fixture.debugElement.query(By.css('button.deselect'));
deselectAllButton.triggerEventHandler('click', null);
expect(comp.deselectAll).toHaveBeenCalled();
});
});
describe('deleteFormats success', () => {
beforeEach(async(() => {
notificationsServiceStub = new NotificationsServiceStub();
scheduler = getTestScheduler();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
getSelectedBitstreamFormats: observableOf(mockFormatsList),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: observableOf(true),
clearBitStreamFormatRequests: observableOf('cleared')
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
{provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
{provide: NotificationsService, useValue: notificationsServiceStub}
]
}).compileComponents();
}
));
beforeEach(initBeforeEach);
it('should clear bitstream formats ', () => {
comp.deleteFormats();
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
expect(notificationsServiceStub.success).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.success.head',
'admin.registries.bitstream-formats.delete.success.amount');
expect(notificationsServiceStub.error).not.toHaveBeenCalled();
});
});
describe('deleteFormats error', () => {
beforeEach(async(() => {
notificationsServiceStub = new NotificationsServiceStub();
scheduler = getTestScheduler();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
getSelectedBitstreamFormats: observableOf(mockFormatsList),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: observableOf(false),
clearBitStreamFormatRequests: observableOf('cleared')
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
{provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
{provide: NotificationsService, useValue: notificationsServiceStub}
]
}).compileComponents();
}
));
beforeEach(initBeforeEach);
it('should clear bitstream formats ', () => {
comp.deleteFormats();
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
expect(notificationsServiceStub.error).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.failure.head',
'admin.registries.bitstream-formats.delete.failure.amount');
expect(notificationsServiceStub.success).not.toHaveBeenCalled();
});
});
});

View File

@@ -1,10 +1,16 @@
import { Component } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service';
import { Observable } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { FindAllOptions } from '../../../core/data/request.models';
import { map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
/**
* This component renders a list of bitstream formats
@@ -13,24 +19,125 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
selector: 'ds-bitstream-formats',
templateUrl: './bitstream-formats.component.html'
})
export class BitstreamFormatsComponent {
export class BitstreamFormatsComponent implements OnInit {
/**
* A paginated list of bitstream formats to be shown on the page
*/
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
/**
* A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats
*/
pageState: BehaviorSubject<string>;
/**
* The current pagination configuration for the page used by the FindAll method
* Currently simply renders all bitstream formats
*/
config: FindAllOptions = Object.assign(new FindAllOptions(), {
elementsPerPage: 20
});
/**
* The current pagination configuration for the page
* Currently simply renders all bitstream formats
*/
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'registry-bitstreamformats-pagination',
pageSize: 10000
pageSize: 20
});
constructor(private registryService: RegistryService) {
this.updateFormats();
constructor(private notificationsService: NotificationsService,
private router: Router,
private translateService: TranslateService,
private bitstreamFormatService: BitstreamFormatDataService) {
}
/**
* Deletes the currently selected formats from the registry and updates the presented list
*/
deleteFormats() {
this.bitstreamFormatService.clearBitStreamFormatRequests().subscribe();
this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(take(1)).subscribe(
(formats) => {
const tasks$ = [];
for (const format of formats) {
if (hasValue(format.id)) {
tasks$.push(this.bitstreamFormatService.delete(format));
}
}
zip(...tasks$).subscribe((results: boolean[]) => {
const successResponses = results.filter((result: boolean) => result);
const failedResponses = results.filter((result: boolean) => !result);
if (successResponses.length > 0) {
this.showNotification(true, successResponses.length);
}
if (failedResponses.length > 0) {
this.showNotification(false, failedResponses.length);
}
this.deselectAll();
this.router.navigate([], {
queryParams: Object.assign({}, { page: 1 }),
queryParamsHandling: 'merge'
}); });
}
);
}
/**
* Deselects all selecetd bitstream formats
*/
deselectAll() {
this.bitstreamFormatService.deselectAllBitstreamFormats();
}
/**
* Checks whether a given bitstream format is selected in the list (checkbox)
* @param bitstreamFormat
*/
isSelected(bitstreamFormat: BitstreamFormat): Observable<boolean> {
return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
map((bitstreamFormats: BitstreamFormat[]) => {
return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null;
})
);
}
/**
* Selects or deselects a bitstream format based on the checkbox state
* @param bitstreamFormat
* @param event
*/
selectBitStreamFormat(bitstreamFormat: BitstreamFormat, event) {
event.target.checked ?
this.bitstreamFormatService.selectBitstreamFormat(bitstreamFormat) :
this.bitstreamFormatService.deselectBitstreamFormat(bitstreamFormat);
}
/**
* Show notifications for an amount of deleted bitstream formats
* @param success Whether or not the notification should be a success message (error message when false)
* @param amount The amount of deleted bitstream formats
*/
private showNotification(success: boolean, amount: number) {
const prefix = 'admin.registries.bitstream-formats.delete';
const suffix = success ? 'success' : 'failure';
const messages = observableCombineLatest(
this.translateService.get(`${prefix}.${suffix}.head`),
this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount})
);
messages.subscribe(([head, content]) => {
if (success) {
this.notificationsService.success(head, content);
} else {
this.notificationsService.error(head, content);
}
});
}
/**
@@ -38,14 +145,26 @@ export class BitstreamFormatsComponent {
* @param event The page change event
*/
onPageChange(event) {
this.config.currentPage = event;
this.updateFormats();
this.config = Object.assign(new FindAllOptions(), this.config, {
currentPage: event,
});
this.pageConfig.currentPage = event;
this.pageState.next('pageChange');
}
ngOnInit(): void {
this.pageState = new BehaviorSubject('init');
this.bitstreamFormats = this.pageState.pipe(
switchMap(() => {
return this.updateFormats()
;
}));
}
/**
* Method to update the bitstream formats that are shown
* Finds all formats based on the current config
*/
private updateFormats() {
this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config);
return this.bitstreamFormatService.findAll(this.config);
}
}

View File

@@ -0,0 +1,30 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { SharedModule } from '../../../shared/shared.module';
import { FormatFormComponent } from './format-form/format-form.component';
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
@NgModule({
imports: [
CommonModule,
SharedModule,
RouterModule,
TranslateModule,
BitstreamFormatsRoutingModule
],
declarations: [
BitstreamFormatsComponent,
EditBitstreamFormatComponent,
AddBitstreamFormatComponent,
FormatFormComponent
],
entryComponents: []
})
export class BitstreamFormatsModule {
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { find } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { hasValue } from '../../../shared/empty.util';
/**
* This class represents a resolver that requests a specific bitstreamFormat before the route is activated
*/
@Injectable()
export class BitstreamFormatsResolver implements Resolve<RemoteData<BitstreamFormat>> {
constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
}
/**
* Method for resolving an bitstreamFormat based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<BitstreamFormat>> Emits the found bitstreamFormat based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
return this.bitstreamFormatDataService.findById(route.params.id)
.pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
);
}
}

View File

@@ -0,0 +1,11 @@
<div class="container">
<div class="row">
<div class="col-12 mb-4">
<h2 id="sub-header"
class="border-bottom mb-2">{{'admin.registries.bitstream-formats.edit.head' | translate:{format: (bitstreamFormatRD$ | async)?.payload.shortDescription} }}</h2>
<ds-bitstream-format-form [bitstreamFormat]="(bitstreamFormatRD$ | async)?.payload" (updatedFormat)="updateFormat($event)"></ds-bitstream-format-form>
</div>
</div>
</div>

View File

@@ -0,0 +1,123 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ActivatedRoute, Router } from '@angular/router';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RouterStub } from '../../../../shared/testing/router-stub';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { ResourceType } from '../../../../core/shared/resource-type';
describe('EditBitstreamFormatComponent', () => {
let comp: EditBitstreamFormatComponent;
let fixture: ComponentFixture<EditBitstreamFormatComponent>;
const bitstreamFormat = new BitstreamFormat();
bitstreamFormat.uuid = 'test-uuid-1';
bitstreamFormat.id = 'test-uuid-1';
bitstreamFormat.shortDescription = 'Unknown';
bitstreamFormat.description = 'Unknown data format';
bitstreamFormat.mimetype = 'application/octet-stream';
bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat.internal = false;
bitstreamFormat.extensions = null;
const routeStub = {
data: observableOf({
bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat)
})
};
let router;
let notificationService: NotificationsServiceStub;
let bitstreamFormatDataService: BitstreamFormatDataService;
const initAsync = () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success'))
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [EditBitstreamFormatComponent],
providers: [
{provide: ActivatedRoute, useValue: routeStub},
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
};
const initBeforeEach = () => {
fixture = TestBed.createComponent(EditBitstreamFormatComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
};
describe('init', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should initialise the bitstreamFormat based on the route', () => {
comp.bitstreamFormatRD$.subscribe((format: RemoteData<BitstreamFormat>) => {
expect(format).toEqual(new RemoteData(false, false, true, null, bitstreamFormat));
});
});
});
describe('updateFormat success', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.updateFormat(bitstreamFormat);
expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.success).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
});
});
describe('updateFormat error', () => {
beforeEach(async( () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request'))
});
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [EditBitstreamFormatComponent],
providers: [
{provide: ActivatedRoute, useValue: routeStub},
{provide: Router, useValue: router},
{provide: NotificationsService, useValue: notificationService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(initBeforeEach);
it('should send the updated form to the service, show a notification and navigate to ', () => {
comp.updateFormat(bitstreamFormat);
expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
expect(notificationService.error).toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,62 @@
import { map, take } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../../../core/data/remote-data';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
import { TranslateService } from '@ngx-translate/core';
/**
* This component renders the edit page of a bitstream format.
* The route parameter 'id' is used to request the bitstream format.
*/
@Component({
selector: 'ds-edit-bitstream-format',
templateUrl: './edit-bitstream-format.component.html',
})
export class EditBitstreamFormatComponent implements OnInit {
/**
* The bitstream format wrapped in a remote-data object
*/
bitstreamFormatRD$: Observable<RemoteData<BitstreamFormat>>;
constructor(
private route: ActivatedRoute,
private router: Router,
private notificationService: NotificationsService,
private translateService: TranslateService,
private bitstreamFormatDataService: BitstreamFormatDataService,
) {
}
ngOnInit(): void {
this.bitstreamFormatRD$ = this.route.data.pipe(
map((data) => data.bitstreamFormat as RemoteData<BitstreamFormat>)
);
}
/**
* Updates the bitstream format based on the provided bitstream format emitted by the form.
* When successful, a success notification will be shown and the user will be navigated back to the overview page.
* When failed, an error notification will be shown.
*/
updateFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1)
).subscribe((response: RestResponse) => {
if (response.isSuccessful) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'),
this.translateService.get('admin.registries.bitstream-formats.edit.success.content'));
this.router.navigate([getBitstreamFormatsModulePath()]);
} else {
this.notificationService.error('admin.registries.bitstream-formats.edit.failure.head',
'admin.registries.bitstream-formats.create.edit.content');
}
}
);
}
}

View File

@@ -0,0 +1,3 @@
<ds-form *ngIf="formModel"
[formId]="'comcol-form-id'"
[formModel]="formModel" (submitForm)="onSubmit()" (cancel)="onCancel()"></ds-form>

View File

@@ -0,0 +1,104 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { RouterStub } from '../../../../shared/testing/router-stub';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { FormatFormComponent } from './format-form.component';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { DynamicCheckboxModel, DynamicFormArrayModel, DynamicInputModel } from '@ng-dynamic-forms/core';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { isEmpty } from '../../../../shared/empty.util';
describe('FormatFormComponent', () => {
let comp: FormatFormComponent;
let fixture: ComponentFixture<FormatFormComponent>;
const router = new RouterStub();
const bitstreamFormat = new BitstreamFormat();
bitstreamFormat.uuid = 'test-uuid-1';
bitstreamFormat.id = 'test-uuid-1';
bitstreamFormat.shortDescription = 'Unknown';
bitstreamFormat.description = 'Unknown data format';
bitstreamFormat.mimetype = 'application/octet-stream';
bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
bitstreamFormat.internal = false;
bitstreamFormat.extensions = [];
const submittedBitstreamFormat = new BitstreamFormat();
submittedBitstreamFormat.id = bitstreamFormat.id;
submittedBitstreamFormat.shortDescription = bitstreamFormat.shortDescription;
submittedBitstreamFormat.mimetype = bitstreamFormat.mimetype;
submittedBitstreamFormat.description = bitstreamFormat.description;
submittedBitstreamFormat.supportLevel = bitstreamFormat.supportLevel;
submittedBitstreamFormat.internal = bitstreamFormat.internal;
submittedBitstreamFormat.extensions = bitstreamFormat.extensions;
const initAsync = () => {
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), ReactiveFormsModule, FormsModule, TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [FormatFormComponent],
providers: [
{provide: Router, useValue: router},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
};
const initBeforeEach = () => {
fixture = TestBed.createComponent(FormatFormComponent);
comp = fixture.componentInstance;
comp.bitstreamFormat = bitstreamFormat;
fixture.detectChanges();
};
describe('initialise', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should initialises the values in the form', () => {
expect((comp.formModel[0] as DynamicInputModel).value).toBe(bitstreamFormat.shortDescription);
expect((comp.formModel[1] as DynamicInputModel).value).toBe(bitstreamFormat.mimetype);
expect((comp.formModel[2] as DynamicInputModel).value).toBe(bitstreamFormat.description);
expect((comp.formModel[3] as DynamicInputModel).value).toBe(bitstreamFormat.supportLevel);
expect((comp.formModel[4] as DynamicCheckboxModel).value).toBe(bitstreamFormat.internal);
const formArray = (comp.formModel[5] as DynamicFormArrayModel);
const extensions = [];
for (let i = 0; i < formArray.groups.length; i++) {
const value = (formArray.get(i).get(0) as DynamicInputModel).value;
if (!isEmpty(value)) {
extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
}
}
expect(extensions).toEqual(bitstreamFormat.extensions);
});
});
describe('onSubmit', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should emit the bitstreamFormat currently present in the form', () => {
spyOn(comp.updatedFormat, 'emit');
comp.onSubmit();
expect(comp.updatedFormat.emit).toHaveBeenCalledWith(submittedBitstreamFormat);
});
});
describe('onCancel', () => {
beforeEach(async(initAsync));
beforeEach(initBeforeEach);
it('should navigate back to the bitstream overview', () => {
comp.onCancel();
expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
});
});
});

View File

@@ -0,0 +1,194 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import {
DynamicCheckboxModel,
DynamicFormArrayModel,
DynamicFormControlLayout, DynamicFormControlLayoutConfig,
DynamicFormControlModel,
DynamicFormService,
DynamicInputModel,
DynamicSelectModel,
DynamicTextAreaModel
} from '@ng-dynamic-forms/core';
import { Router } from '@angular/router';
import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
import { hasValue, isEmpty } from '../../../../shared/empty.util';
import { TranslateService } from '@ngx-translate/core';
/**
* The component responsible for rendering the form to create/edit a bitstream format
*/
@Component({
selector: 'ds-bitstream-format-form',
templateUrl: './format-form.component.html'
})
export class FormatFormComponent implements OnInit {
/**
* The current bitstream format
* This can either be and existing one or a new one
*/
@Input() bitstreamFormat: BitstreamFormat = new BitstreamFormat();
/**
* EventEmitter that will emit the updated bitstream format
*/
@Output() updatedFormat: EventEmitter<BitstreamFormat> = new EventEmitter<BitstreamFormat>();
/**
* The different supported support level of the bitstream format
*/
supportLevelOptions = [{label: BitstreamFormatSupportLevel.Known, value: BitstreamFormatSupportLevel.Known},
{label: BitstreamFormatSupportLevel.Unknown, value: BitstreamFormatSupportLevel.Unknown},
{label: BitstreamFormatSupportLevel.Supported, value: BitstreamFormatSupportLevel.Supported}];
/**
* Styling element for repeatable field
*/
arrayElementLayout: DynamicFormControlLayout = {
grid: {
group: 'form-row',
},
};
/**
* Styling element for element of repeatable field
*/
arrayInputElementLayout: DynamicFormControlLayout = {
grid: {
host: 'col'
}
};
/**
* The form model representing the bitstream format
*/
formModel: DynamicFormControlModel[] = [
new DynamicInputModel({
id: 'shortDescription',
name: 'shortDescription',
label: 'admin.registries.bitstream-formats.edit.shortDescription.label',
hint: 'admin.registries.bitstream-formats.edit.shortDescription.hint',
required: true,
validators: {
required: null
},
errorMessages: {
required: 'Please enter a name for this bitstream format'
},
}),
new DynamicInputModel({
id: 'mimetype',
name: 'mimetype',
label: 'admin.registries.bitstream-formats.edit.mimetype.label',
hint: 'admin.registries.bitstream-formats.edit.mimetype.hint',
}),
new DynamicTextAreaModel({
id: 'description',
name: 'description',
label: 'admin.registries.bitstream-formats.edit.description.label',
hint: 'admin.registries.bitstream-formats.edit.description.hint',
}),
new DynamicSelectModel({
id: 'supportLevel',
name: 'supportLevel',
options: this.supportLevelOptions,
label: 'admin.registries.bitstream-formats.edit.supportLevel.label',
hint: 'admin.registries.bitstream-formats.edit.supportLevel.hint',
value: this.supportLevelOptions[0].value
}),
new DynamicCheckboxModel({
id: 'internal',
name: 'internal',
label: 'Internal',
hint: 'admin.registries.bitstream-formats.edit.internal.hint',
}),
new DynamicFormArrayModel({
id: 'extensions',
name: 'extensions',
label: 'admin.registries.bitstream-formats.edit.extensions.label',
groupFactory: () => [
new DynamicInputModel({
id: 'extension',
placeholder: 'admin.registries.bitstream-formats.edit.extensions.placeholder',
}, this.arrayInputElementLayout)
]
}, this.arrayElementLayout),
];
constructor(private dynamicFormService: DynamicFormService,
private translateService: TranslateService,
private router: Router) {
}
ngOnInit(): void {
this.initValues();
}
/**
* Initializes the form based on the provided bitstream format
*/
initValues() {
this.formModel.forEach(
(fieldModel: DynamicFormControlModel) => {
if (fieldModel.name === 'extensions') {
if (hasValue(this.bitstreamFormat.extensions)) {
const extenstions = this.bitstreamFormat.extensions;
const formArray = (fieldModel as DynamicFormArrayModel);
for (let i = 0; i < extenstions.length; i++) {
formArray.insertGroup(i).group[0] = new DynamicInputModel({
id: `extension-${i}`,
value: extenstions[i]
}, this.arrayInputElementLayout);
}
}
} else {
if (hasValue(this.bitstreamFormat[fieldModel.name])) {
(fieldModel as DynamicInputModel).value = this.bitstreamFormat[fieldModel.name];
}
}
});
}
/**
* Creates an updated bistream format based on the current values in the form
* Emits the updated bitstream format trouhg the updatedFormat emitter
*/
onSubmit() {
const updatedBitstreamFormat = Object.assign(new BitstreamFormat(),
{
id: this.bitstreamFormat.id
});
this.formModel.forEach(
(fieldModel: DynamicFormControlModel) => {
if (fieldModel.name === 'extensions') {
const formArray = (fieldModel as DynamicFormArrayModel);
const extensions = [];
for (let i = 0; i < formArray.groups.length; i++) {
const value = (formArray.get(i).get(0) as DynamicInputModel).value;
if (!isEmpty(value)) {
extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
}
}
updatedBitstreamFormat.extensions = extensions;
} else {
updatedBitstreamFormat[fieldModel.name] = (fieldModel as DynamicInputModel).value;
}
});
this.updatedFormat.emit(updatedBitstreamFormat);
}
/**
* Cancels the edit/create action of the bitstream format and navigates back to the bitstream format registry
*/
onCancel() {
this.router.navigate([getBitstreamFormatsModulePath()]);
}
}

View File

@@ -1,11 +1,19 @@
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { URLCombiner } from '../core/url-combiner/url-combiner';
import { getAdminModulePath } from '../app-routing.module';
const REGISTRIES_MODULE_PATH = 'registries';
export function getRegistriesModulePath() {
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
}
@NgModule({
imports: [
RouterModule.forChild([
{
path: 'registries',
path: REGISTRIES_MODULE_PATH,
loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
}
])

View File

@@ -52,6 +52,9 @@
message="{{'error.recent-submissions' | translate}}"></ds-error>
<ds-loading *ngIf="!itemRD || itemRD.isLoading"
message="{{'loading.recent-submissions' | translate}}"></ds-loading>
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
{{'collection.page.browse.recent.empty' | translate}}
</div>
</ng-container>
</div>
<ds-error *ngIf="collectionRD?.hasFailed"

View File

@@ -62,7 +62,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
GenericItemPageFieldComponent,
RelatedEntitiesSearchComponent,
RelatedItemsComponent,
MetadataRepresentationListComponent
MetadataRepresentationListComponent,
ItemPageTitleFieldComponent
],
entryComponents: [
PublicationComponent

View File

@@ -16,6 +16,12 @@ const COMMUNITY_MODULE_PATH = 'communities';
export function getCommunityModulePath() {
return `/${COMMUNITY_MODULE_PATH}`;
}
const ADMIN_MODULE_PATH = 'admin';
export function getAdminModulePath() {
return `/${ADMIN_MODULE_PATH}`;
}
@NgModule({
imports: [
RouterModule.forRoot([
@@ -27,7 +33,7 @@ export function getCommunityModulePath() {
{ path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', canActivate: [AuthenticatedGuard] },
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
{ path: 'admin', loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
{ path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },

View File

@@ -23,6 +23,10 @@ import { hasValue } from './shared/empty.util';
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
import { historyReducer, HistoryState } from './shared/history/history.reducer';
import {
bitstreamFormatReducer,
BitstreamFormatRegistryState
} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
export interface AppState {
router: fromRouter.RouterReducerState;
@@ -30,6 +34,7 @@ export interface AppState {
hostWindow: HostWindowState;
forms: FormState;
metadataRegistry: MetadataRegistryState;
bitstreamFormats: BitstreamFormatRegistryState;
notifications: NotificationsState;
searchSidebar: SearchSidebarState;
searchFilter: SearchFiltersState;
@@ -44,6 +49,7 @@ export const appReducers: ActionReducerMap<AppState> = {
hostWindow: hostWindowReducer,
forms: formReducer,
metadataRegistry: metadataRegistryReducer,
bitstreamFormats: bitstreamFormatReducer,
notifications: notificationsReducer,
searchSidebar: sidebarReducer,
searchFilter: filterReducer,

View File

@@ -4,7 +4,7 @@ import { BitstreamFormat } from '../../shared/bitstream-format.model';
import { mapsTo } from '../builders/build-decorators';
import { IDToUUIDSerializer } from '../id-to-uuid-serializer';
import { NormalizedObject } from './normalized-object.model';
import { SupportLevel } from './support-level.model';
import { BitstreamFormatSupportLevel } from '../../shared/bitstream-format-support-level';
/**
* Normalized model class for a Bitstream Format
@@ -34,7 +34,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat>
* The level of support the system offers for this Bitstream Format
*/
@autoserialize
supportLevel: SupportLevel;
supportLevel: BitstreamFormatSupportLevel;
/**
* True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -46,7 +46,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject<BitstreamFormat>
* String representing this Bitstream Format's file extension
*/
@autoserialize
extensions: string;
extensions: string[];
/**
* Identifier for this Bitstream Format

View File

@@ -107,6 +107,7 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing
import { ClaimedTaskDataService } from './tasks/claimed-task-data.service';
import { PoolTaskDataService } from './tasks/pool-task-data.service';
import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
import { BitstreamFormatDataService } from './data/bitstream-format-data.service';
import { NormalizedClaimedTask } from './tasks/models/normalized-claimed-task-object.model';
import { NormalizedTaskObject } from './tasks/models/normalized-task-object.model';
import { NormalizedPoolTask } from './tasks/models/normalized-pool-task-object.model';
@@ -153,6 +154,7 @@ const PROVIDERS = [
PaginationComponentOptions,
ResourcePolicyService,
RegistryService,
BitstreamFormatDataService,
NormalizedObjectBuildService,
RemoteDataBuildService,
RequestService,

View File

@@ -0,0 +1,293 @@
import { BitstreamFormatDataService } from './bitstream-format-data.service';
import { RequestEntry } from './request.reducer';
import { RestResponse } from '../cache/response.models';
import { Observable, of as observableOf } from 'rxjs';
import { Action, Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { BitstreamFormat } from '../shared/bitstream-format.model';
import { async } from '@angular/core/testing';
import {
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistryDeselectAllAction,
BitstreamFormatsRegistrySelectAction
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
import { TestScheduler } from 'rxjs/testing';
describe('BitstreamFormatDataService', () => {
let service: BitstreamFormatDataService;
let requestService;
let scheduler: TestScheduler;
const bitstreamFormatsEndpoint = 'https://rest.api/core/bitstream-formats';
const bitstreamFormatsIdEndpoint = 'https://rest.api/core/bitstream-formats/format-id';
const responseCacheEntry = new RequestEntry();
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
responseCacheEntry.completed = true;
const store = {
dispatch(action: Action) {
// Do Nothing
}
} as Store<CoreState>;
const objectCache = {} as ObjectCacheService;
const halEndpointService = {
getEndpoint(linkPath: string): Observable<string> {
return cold('a', {a: bitstreamFormatsEndpoint});
}
} as HALEndpointService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
const comparator = {} as any;
const dataBuildService = {} as NormalizedObjectBuildService;
const rdbService = {} as RemoteDataBuildService;
function initTestService(halService) {
return new BitstreamFormatDataService(
requestService,
rdbService,
dataBuildService,
store,
objectCache,
halService,
notificationsService,
http,
comparator
);
}
describe('getBrowseEndpoint', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should get the browse endpoint', () => {
const result = service.getBrowseEndpoint();
const expected = cold('b', {b: bitstreamFormatsEndpoint});
expect(result).toBeObservable(expected);
});
});
describe('getUpdateEndpoint', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should get the update endpoint', () => {
const formatId = 'format-id';
const result = service.getUpdateEndpoint(formatId);
const expected = cold('b', {b: bitstreamFormatsIdEndpoint});
expect(result).toBeObservable(expected);
});
});
describe('getCreateEndpoint', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should get the create endpoint ', () => {
const result = service.getCreateEndpoint();
const expected = cold('b', {b: bitstreamFormatsEndpoint});
expect(result).toBeObservable(expected);
});
});
describe('updateBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should update the bitstream format', () => {
const updatedBistreamFormat = new BitstreamFormat();
updatedBistreamFormat.uuid = 'updated-uuid';
const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
const result = service.updateBitstreamFormat(updatedBistreamFormat);
expect(result).toBeObservable(expected);
});
});
describe('createBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
}));
it('should create a new bitstream format', () => {
const newFormat = new BitstreamFormat();
newFormat.uuid = 'new-uuid';
const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
const result = service.createBitstreamFormat(newFormat);
expect(result).toBeObservable(expected);
});
});
describe('clearBitStreamFormatRequests', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
const halService = {
getEndpoint(linkPath: string): Observable<string> {
return observableOf(bitstreamFormatsEndpoint);
}
} as HALEndpointService;
service = initTestService(halService);
service.clearBitStreamFormatRequests().subscribe();
}));
it('should remove the bitstream format hrefs in the request service', () => {
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(bitstreamFormatsEndpoint);
});
});
describe('selectBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
spyOn(store, 'dispatch');
}));
it('should add a selected bitstream to the store', () => {
const format = new BitstreamFormat();
format.uuid = 'uuid';
service.selectBitstreamFormat(format);
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistrySelectAction(format));
});
});
describe('deselectBitstreamFormat', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
spyOn(store, 'dispatch');
}));
it('should remove a bitstream from the store', () => {
const format = new BitstreamFormat();
format.uuid = 'uuid';
service.deselectBitstreamFormat(format);
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAction(format));
});
});
describe('deselectAllBitstreamFormats', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
service = initTestService(halEndpointService);
spyOn(store, 'dispatch');
}));
it('should remove all bitstreamFormats from the store', () => {
service.deselectAllBitstreamFormats();
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAllAction());
});
});
describe('delete', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: hot('a', {a: responseCacheEntry}),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
const halService = {
getEndpoint(linkPath: string): Observable<string> {
return observableOf(bitstreamFormatsEndpoint);
}
} as HALEndpointService;
service = initTestService(halService);
}));
it('should delete a bitstream format', () => {
const format = new BitstreamFormat();
format.uuid = 'format-uuid';
format.id = 'format-id';
const expected = cold('(b|)', {b: true});
const result = service.delete(format);
expect(result).toBeObservable(expected);
});
});
});

View File

@@ -0,0 +1,183 @@
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { BitstreamFormat } from '../shared/bitstream-format.model';
import { RequestService } from './request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { createSelector, select, Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models';
import { Observable } from 'rxjs';
import { find, map, tap } from 'rxjs/operators';
import { configureRequest, getResponseFromEntry } from '../shared/operators';
import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
import { RestResponse } from '../cache/response.models';
import { AppState } from '../../app.reducer';
import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
import {
BitstreamFormatsRegistryDeselectAction,
BitstreamFormatsRegistryDeselectAllAction,
BitstreamFormatsRegistrySelectAction
} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
import { hasValue } from '../../shared/empty.util';
import { RequestEntry } from './request.reducer';
const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats;
const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector,
(bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats);
/**
* A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint
*/
@Injectable()
export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
protected linkPath = 'bitstreamformats';
protected forceBypassCache = false;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected dataBuildService: NormalizedObjectBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<BitstreamFormat>) {
super();
}
/**
* Get the endpoint for browsing bitstream formats
* @param {FindAllOptions} options
* @returns {Observable<string>}
*/
getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
/**
* Get the endpoint to update an existing bitstream format
* @param formatId
*/
public getUpdateEndpoint(formatId: string): Observable<string> {
return this.getBrowseEndpoint().pipe(
map((endpoint: string) => this.getIDHref(endpoint, formatId))
);
}
/**
* Get the endpoint to create a new bitstream format
*/
public getCreateEndpoint(): Observable<string> {
return this.getBrowseEndpoint();
}
/**
* Update an existing bitstreamFormat
* @param bitstreamFormat
*/
updateBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
const requestId = this.requestService.generateRequestId();
this.getUpdateEndpoint(bitstreamFormat.id).pipe(
distinctUntilChanged(),
map((endpointURL: string) =>
new PutRequest(requestId, endpointURL, bitstreamFormat)),
configureRequest(this.requestService)).subscribe();
return this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
);
}
/**
* Create a new BitstreamFormat
* @param BitstreamFormat
*/
public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable<RestResponse> {
const requestId = this.requestService.generateRequestId();
this.getCreateEndpoint().pipe(
map((endpointURL: string) => {
return new PostRequest(requestId, endpointURL, bitstreamFormat);
}),
configureRequest(this.requestService)
).subscribe();
return this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
);
}
/**
* Clears the cache of the list of BitstreamFormats
*/
public clearBitStreamFormatRequests(): Observable<string> {
return this.getBrowseEndpoint().pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
);
}
/**
* Gets all the selected BitstreamFormats from the store
*/
public getSelectedBitstreamFormats(): Observable<BitstreamFormat[]> {
return this.store.pipe(select(selectedBitstreamFormatSelector));
}
/**
* Adds a BistreamFormat to the selected BitstreamFormats in the store
* @param bitstreamFormat
*/
public selectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.store.dispatch(new BitstreamFormatsRegistrySelectAction(bitstreamFormat));
}
/**
* Removes a BistreamFormat from the list of selected BitstreamFormats in the store
* @param bitstreamFormat
*/
public deselectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.store.dispatch(new BitstreamFormatsRegistryDeselectAction(bitstreamFormat));
}
/**
* Removes all BitstreamFormats from the list of selected BitstreamFormats in the store
*/
public deselectAllBitstreamFormats() {
this.store.dispatch(new BitstreamFormatsRegistryDeselectAllAction());
}
/**
* Delete an existing DSpace Object on the server
* @param format The DSpace Object to be removed
* Return an observable that emits true when the deletion was successful, false when it failed
*/
delete(format: BitstreamFormat): Observable<boolean> {
const requestId = this.requestService.generateRequestId();
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, format.id)));
hrefObs.pipe(
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new DeleteByIDRequest(requestId, href, format.id);
this.requestService.configure(request);
})
).subscribe();
return this.requestService.getByUUID(requestId).pipe(
find((request: RequestEntry) => request.completed),
map((request: RequestEntry) => request.response.isSuccessful)
);
}
}

View File

@@ -1,8 +0,0 @@
export class BitstreamFormat {
shortDescription: string;
description: string;
mimetype: string;
supportLevel: number;
internal: boolean;
extensions: string;
}

View File

@@ -12,7 +12,6 @@ import { PageInfo } from '../shared/page-info.model';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import {
RegistryBitstreamformatsSuccessResponse,
RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse,
RestResponse
@@ -20,7 +19,6 @@ import {
import { Component } from '@angular/core';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
import { map } from 'rxjs/operators';
import { Store, StoreModule } from '@ngrx/store';
import { MockStore } from '../../shared/testing/mock-store';
@@ -44,7 +42,7 @@ import { MetadataSchema } from '../metadata/metadata-schema.model';
import { MetadataField } from '../metadata/metadata-field.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
@Component({ template: '' })
@Component({template: ''})
class DummyComponent {
}
@@ -127,7 +125,7 @@ describe('RegistryService', () => {
toRemoteDataObservable: (requestEntryObs: Observable<RequestEntry>, payloadObs: Observable<any>) => {
return observableCombineLatest(requestEntryObs,
payloadObs).pipe(map(([req, pay]) => {
return { req, pay };
return {req, pay};
})
);
},
@@ -143,11 +141,11 @@ describe('RegistryService', () => {
DummyComponent
],
providers: [
{ provide: RequestService, useValue: getMockRequestService() },
{ provide: RemoteDataBuildService, useValue: rdbStub },
{ provide: HALEndpointService, useValue: halServiceStub },
{ provide: Store, useClass: MockStore },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
{provide: RequestService, useValue: getMockRequestService()},
{provide: RemoteDataBuildService, useValue: rdbStub},
{provide: HALEndpointService, useValue: halServiceStub},
{provide: Store, useClass: MockStore},
{provide: NotificationsService, useValue: new NotificationsServiceStub()},
RegistryService
]
});
@@ -162,7 +160,7 @@ describe('RegistryService', () => {
page: pageInfo
});
const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo);
const responseEntry = Object.assign(new RequestEntry(), { response: response });
const responseEntry = Object.assign(new RequestEntry(), {response: response});
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -191,7 +189,7 @@ describe('RegistryService', () => {
page: pageInfo
});
const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo);
const responseEntry = Object.assign(new RequestEntry(), { response: response });
const responseEntry = Object.assign(new RequestEntry(), {response: response});
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -220,7 +218,7 @@ describe('RegistryService', () => {
page: pageInfo
});
const response = new RegistryMetadatafieldsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
const responseEntry = Object.assign(new RequestEntry(), { response: response });
const responseEntry = Object.assign(new RequestEntry(), {response: response});
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -243,35 +241,6 @@ describe('RegistryService', () => {
});
});
describe('when requesting bitstreamformats', () => {
const queryResponse = Object.assign(new RegistryBitstreamformatsResponse(), {
bitstreamformats: mockFieldsList,
page: pageInfo
});
const response = new RegistryBitstreamformatsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
const responseEntry = Object.assign(new RequestEntry(), { response: response });
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
/* tslint:disable:no-empty */
registryService.getBitstreamFormats(pagination).subscribe((value) => {
});
/* tslint:enable:no-empty */
});
it('should call getEndpoint on the halService', () => {
expect((registryService as any).halService.getEndpoint).toHaveBeenCalled();
});
it('should send out the request on the request service', () => {
expect((registryService as any).requestService.configure).toHaveBeenCalled();
});
it('should call getByHref on the request service with the correct request url', () => {
expect((registryService as any).requestService.getByHref).toHaveBeenCalledWith(endpointWithParams);
});
});
describe('when dispatching to the store', () => {
beforeEach(() => {
spyOn(mockStore, 'dispatch');
@@ -284,7 +253,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryEditSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditSchemaAction(mockSchemasList[0]));
})
});
});
describe('when calling cancelEditMetadataSchema', () => {
@@ -294,7 +263,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryCancelSchemaAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelSchemaAction());
})
});
});
describe('when calling selectMetadataSchema', () => {
@@ -304,7 +273,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistrySelectSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectSchemaAction(mockSchemasList[0]));
})
});
});
describe('when calling deselectMetadataSchema', () => {
@@ -314,7 +283,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectSchemaAction(mockSchemasList[0]));
})
});
});
describe('when calling deselectAllMetadataSchema', () => {
@@ -324,7 +293,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectAllSchemaAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllSchemaAction());
})
});
});
describe('when calling editMetadataField', () => {
@@ -334,7 +303,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryEditFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditFieldAction(mockFieldsList[0]));
})
});
});
describe('when calling cancelEditMetadataField', () => {
@@ -344,7 +313,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryCancelFieldAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelFieldAction());
})
});
});
describe('when calling selectMetadataField', () => {
@@ -354,7 +323,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistrySelectFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectFieldAction(mockFieldsList[0]));
})
});
});
describe('when calling deselectMetadataField', () => {
@@ -364,7 +333,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectFieldAction(mockFieldsList[0]));
})
});
});
describe('when calling deselectAllMetadataField', () => {
@@ -374,7 +343,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectAllFieldAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllFieldAction());
})
});
});
});
@@ -417,7 +386,7 @@ describe('RegistryService', () => {
result.subscribe((response: RestResponse) => {
expect(response.isSuccessful).toBe(true);
});
})
});
});
describe('when deleteMetadataField is called', () => {
@@ -431,7 +400,7 @@ describe('RegistryService', () => {
result.subscribe((response: RestResponse) => {
expect(response.isSuccessful).toBe(true);
});
})
});
});
describe('when clearMetadataSchemaRequests is called', () => {

View File

@@ -3,13 +3,13 @@ import { Injectable } from '@angular/core';
import { RemoteData } from '../data/remote-data';
import { PaginatedList } from '../data/paginated-list';
import { PageInfo } from '../shared/page-info.model';
import { BitstreamFormat } from './mock-bitstream-format.model';
import {
CreateMetadataFieldRequest,
CreateMetadataSchemaRequest,
DeleteRequest,
GetRequest,
RestRequest, UpdateMetadataFieldRequest,
RestRequest,
UpdateMetadataFieldRequest,
UpdateMetadataSchemaRequest
} from '../data/request.models';
import { GenericConstructor } from '../shared/generic-constructor';
@@ -19,24 +19,19 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
import { RequestService } from '../data/request.service';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import {
ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse,
RegistryBitstreamformatsSuccessResponse,
MetadatafieldSuccessResponse,
MetadataschemaSuccessResponse,
RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse, RestResponse
RegistryMetadataschemasSuccessResponse,
RestResponse
} from '../cache/response.models';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service';
import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
import { hasValue, hasNoValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { URLCombiner } from '../url-combiner/url-combiner';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service';
import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
import {
configureRequest,
getResponseFromEntry,
getSucceededRemoteData
} from '../shared/operators';
import { configureRequest, getResponseFromEntry } from '../shared/operators';
import { createSelector, select, Store } from '@ngrx/store';
import { AppState } from '../../app.reducer';
import { MetadataRegistryState } from '../../+admin/admin-registries/metadata-registry/metadata-registry.reducers';
@@ -52,9 +47,8 @@ import {
MetadataRegistrySelectFieldAction,
MetadataRegistrySelectSchemaAction
} from '../../+admin/admin-registries/metadata-registry/metadata-registry.actions';
import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
import { distinctUntilChanged, flatMap, map, take, tap } from 'rxjs/operators';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { ResourceType } from '../shared/resource-type';
import { NormalizedMetadataSchema } from '../metadata/normalized-metadata-schema.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
@@ -79,7 +73,8 @@ export class RegistryService {
private metadataSchemasPath = 'metadataschemas';
private metadataFieldsPath = 'metadatafields';
private bitstreamFormatsPath = 'bitstreamformats';
// private bitstreamFormatsPath = 'bitstreamformats';
constructor(protected requestService: RequestService,
private rdb: RemoteDataBuildService,
@@ -197,7 +192,7 @@ export class RegistryService {
*/
public getAllMetadataFields(pagination?: PaginationComponentOptions): Observable<RemoteData<PaginatedList<MetadataField>>> {
if (hasNoValue(pagination)) {
pagination = { currentPage: 1, pageSize: 10000 } as any;
pagination = {currentPage: 1, pageSize: 10000} as any;
}
const requestObs = this.getMetadataFieldsRequestObs(pagination);
@@ -231,41 +226,7 @@ export class RegistryService {
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
}
/**
* Retrieves all bitstream formats
* @param pagination The pagination info used to retrieve the bitstream formats
*/
public getBitstreamFormats(pagination: PaginationComponentOptions): Observable<RemoteData<PaginatedList<BitstreamFormat>>> {
const requestObs = this.getBitstreamFormatsRequestObs(pagination);
const requestEntryObs = requestObs.pipe(
flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
);
const rbrObs: Observable<RegistryBitstreamformatsResponse> = requestEntryObs.pipe(
getResponseFromEntry(),
map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse)
);
const bitstreamformatsObs: Observable<BitstreamFormat[]> = rbrObs.pipe(
map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats)
);
const pageInfoObs: Observable<PageInfo> = requestEntryObs.pipe(
getResponseFromEntry(),
map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo)
);
const payloadObs = observableCombineLatest(bitstreamformatsObs, pageInfoObs).pipe(
map(([bitstreamformats, pageInfo]) => {
return new PaginatedList(pageInfo, bitstreamformats);
})
);
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
}
private getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
public getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
map((url: string) => {
const args: string[] = [];
@@ -327,30 +288,6 @@ export class RegistryService {
);
}
private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe(
map((url: string) => {
const args: string[] = [];
args.push(`size=${pagination.pageSize}`);
args.push(`page=${pagination.currentPage - 1}`);
if (isNotEmpty(args)) {
url = new URLCombiner(url, `?${args.join('&')}`).toString();
}
const request = new GetRequest(this.requestService.generateRequestId(), url);
return Object.assign(request, {
getResponseParser(): GenericConstructor<ResponseParsingService> {
return RegistryBitstreamformatsResponseParsingService;
}
});
}),
tap((request: RestRequest) => this.requestService.configure(request)),
);
}
/**
* Method to start editing a metadata schema, dispatches an edit schema action
* @param schema The schema that's being edited
*/
public editMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistryEditSchemaAction(schema));
}
@@ -374,7 +311,7 @@ export class RegistryService {
* @param schema The schema that's being selected
*/
public selectMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema))
this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema));
}
/**
@@ -382,14 +319,14 @@ export class RegistryService {
* @param schema The schema that's it being deselected
*/
public deselectMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema))
this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema));
}
/**
* Method to deselect all currently selected metadata schema, dispatches a deselect all schema action
*/
public deselectAllMetadataSchema() {
this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction())
this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction());
}
/**
@@ -423,20 +360,20 @@ export class RegistryService {
* @param field The field that's being selected
*/
public selectMetadataField(field: MetadataField) {
this.store.dispatch(new MetadataRegistrySelectFieldAction(field))
this.store.dispatch(new MetadataRegistrySelectFieldAction(field));
}
/**
* Method to deselect a metadata field, dispatches a deselect field action
* @param field The field that's it being deselected
*/
public deselectMetadataField(field: MetadataField) {
this.store.dispatch(new MetadataRegistryDeselectFieldAction(field))
this.store.dispatch(new MetadataRegistryDeselectFieldAction(field));
}
/**
* Method to deselect all currently selected metadata fields, dispatches a deselect all field action
*/
public deselectAllMetadataField() {
this.store.dispatch(new MetadataRegistryDeselectAllFieldAction())
this.store.dispatch(new MetadataRegistryDeselectAllFieldAction());
}
/**
@@ -494,7 +431,7 @@ export class RegistryService {
this.notificationsService.error('Server Error:', (response as any).errorMessage, new NotificationOptions(-1));
}
} else {
this.showNotifications(true, isUpdate, false, { prefix: schema.prefix });
this.showNotifications(true, isUpdate, false, {prefix: schema.prefix});
return response;
}
}),
@@ -521,7 +458,7 @@ export class RegistryService {
public clearMetadataSchemaRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
)
);
}
/**
@@ -571,7 +508,7 @@ export class RegistryService {
}
} else {
const fieldString = `${field.schema.prefix}.${field.element}${field.qualifier ? `.${field.qualifier}` : ''}`;
this.showNotifications(true, isUpdate, true, { field: fieldString });
this.showNotifications(true, isUpdate, true, {field: fieldString});
return response;
}
}),
@@ -597,7 +534,7 @@ export class RegistryService {
public clearMetadataFieldRequests(): Observable<string> {
return this.halService.getEndpoint(this.metadataFieldsPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
)
);
}
private delete(path: string, id: number): Observable<RestResponse> {
@@ -633,9 +570,9 @@ export class RegistryService {
);
messages.subscribe(([head, content]) => {
if (success) {
this.notificationsService.success(head, content)
this.notificationsService.success(head, content);
} else {
this.notificationsService.error(head, content)
this.notificationsService.error(head, content);
}
});
}

View File

@@ -0,0 +1,5 @@
export enum BitstreamFormatSupportLevel {
Known = 'KNOWN',
Unknown = 'UNKNOWN',
Supported = 'SUPPORTED'
}

View File

@@ -1,6 +1,6 @@
import { CacheableObject, TypedObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type';
import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
/**
* Model class for a Bitstream Format
@@ -27,7 +27,7 @@ export class BitstreamFormat implements CacheableObject {
/**
* The level of support the system offers for this Bitstream Format
*/
supportLevel: number;
supportLevel: BitstreamFormatSupportLevel;
/**
* True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -37,7 +37,7 @@ export class BitstreamFormat implements CacheableObject {
/**
* String representing this Bitstream Format's file extension
*/
extensions: string;
extensions: string[];
/**
* The link to the rest endpoint where this Bitstream Format can be found
@@ -49,4 +49,11 @@ export class BitstreamFormat implements CacheableObject {
*/
uuid: string;
/**
* Identifier for this Bitstream Format
* Note that this ID is unique for bitstream formats,
* but might not be unique across different object types
*/
id: string;
}

View File

@@ -0,0 +1,30 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('creativework.datePublished')" class="item-date card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('creativework.datePublished')"></span>
</ds-truncatable-part>
</p>
<p *ngIf="dso.hasMetadata('journal.title')" class="item-journal-title card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('journal.title')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,47 @@
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'creativework.datePublished': [
{
language: null,
value: '2015-06-26'
}
],
'journal.title': [
{
language: 'en_US',
value: 'The journal title'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('JournalIssueGridElementComponent', getEntityGridElementTestComponent(JournalIssueGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'journal-title']));

View File

@@ -0,0 +1,17 @@
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { Component } from '@angular/core';
import { focusShadow } from '../../../../shared/animations/focus';
import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
@rendersItemType('JournalIssue', ItemViewMode.Card)
@Component({
selector: 'ds-journal-issue-grid-element',
styleUrls: ['./journal-issue-grid-element.component.scss'],
templateUrl: './journal-issue-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Journal Issue
*/
export class JournalIssueGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -0,0 +1,30 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('creativework.datePublished')" class="item-date card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('creativework.datePublished')"></span>
</ds-truncatable-part>
</p>
<p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('dc.description')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,47 @@
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'creativework.datePublished': [
{
language: null,
value: '2015-06-26'
}
],
'dc.description': [
{
language: 'en_US',
value: 'A description for the journal volume'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('JournalVolumeGridElementComponent', getEntityGridElementTestComponent(JournalVolumeGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'description']));

View File

@@ -0,0 +1,17 @@
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { Component } from '@angular/core';
import { focusShadow } from '../../../../shared/animations/focus';
import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
@rendersItemType('JournalVolume', ItemViewMode.Card)
@Component({
selector: 'ds-journal-volume-grid-element',
styleUrls: ['./journal-volume-grid-element.component.scss'],
templateUrl: './journal-volume-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Journal Volume
*/
export class JournalVolumeGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -0,0 +1,35 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('creativework.editor')"
class="item-publisher card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span class="item-editor">{{dso.firstMetadataValue('creativework.editor')}}</span>
<span *ngIf="dso.hasMetadata('creativework.publisher')" class="item-publisher">
<span>, </span>
{{dso.firstMetadataValue('creativework.publisher')}}
</span>
</ds-truncatable-part>
</p>
<p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('dc.description')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,53 @@
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalGridElementComponent } from './journal-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'creativework.editor': [
{
language: 'en_US',
value: 'Smith, Donald'
}
],
'creativework.publisher': [
{
language: 'en_US',
value: 'A company'
}
],
'dc.description': [
{
language: 'en_US',
value: 'This is the description'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('JournalGridElementComponent', getEntityGridElementTestComponent(JournalGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['editor', 'publisher', 'description']));

View File

@@ -0,0 +1,17 @@
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { Component } from '@angular/core';
import { focusShadow } from '../../../../shared/animations/focus';
import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
@rendersItemType('Journal', ItemViewMode.Card)
@Component({
selector: 'ds-journal-grid-element',
styleUrls: ['./journal-grid-element.component.scss'],
templateUrl: './journal-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Journal
*/
export class JournalGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -9,6 +9,9 @@ import { JournalListElementComponent } from './item-list-elements/journal/journa
import { JournalIssueListElementComponent } from './item-list-elements/journal-issue/journal-issue-list-element.component';
import { JournalVolumeListElementComponent } from './item-list-elements/journal-volume/journal-volume-list-element.component';
import { TooltipModule } from 'ngx-bootstrap';
import { JournalIssueGridElementComponent } from './item-grid-elements/journal-issue/journal-issue-grid-element.component';
import { JournalVolumeGridElementComponent } from './item-grid-elements/journal-volume/journal-volume-grid-element.component';
import { JournalGridElementComponent } from './item-grid-elements/journal/journal-grid-element.component';
const ENTRY_COMPONENTS = [
JournalComponent,
@@ -16,7 +19,10 @@ const ENTRY_COMPONENTS = [
JournalVolumeComponent,
JournalListElementComponent,
JournalIssueListElementComponent,
JournalVolumeListElementComponent
JournalVolumeListElementComponent,
JournalIssueGridElementComponent,
JournalVolumeGridElementComponent,
JournalGridElementComponent
];
@NgModule({

View File

@@ -0,0 +1,35 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('organization.legalName')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('organization.foundingDate')" class="item-date card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('organization.foundingDate')"></span>
</ds-truncatable-part>
</p>
<p *ngIf="dso.hasMetadata('organization.address.addressCountry')"
class="item-location card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span class="item-country">{{dso.firstMetadataValue('organization.address.addressCountry')}}</span>
<span *ngIf="dso.hasMetadata('organization.address.addressLocality')" class="item-city">
<span>, </span>
{{dso.firstMetadataValue('organization.address.addressLocality')}}
</span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,53 @@
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { OrgunitGridElementComponent } from './orgunit-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'organization.foundingDate': [
{
language: null,
value: '2015-06-26'
}
],
'organization.address.addressCountry': [
{
language: 'en_US',
value: 'Belgium'
}
],
'organization.address.addressLocality': [
{
language: 'en_US',
value: 'Brussels'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('OrgunitGridElementComponent', getEntityGridElementTestComponent(OrgunitGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'country', 'city']));

View File

@@ -0,0 +1,17 @@
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { Component } from '@angular/core';
import { focusShadow } from '../../../../shared/animations/focus';
import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
@rendersItemType('OrgUnit', ItemViewMode.Card)
@Component({
selector: 'ds-orgunit-grid-element',
styleUrls: ['./orgunit-grid-element.component.scss'],
templateUrl: './orgunit-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Organisation Unit
*/
export class OrgunitGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -0,0 +1,30 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('person.familyName') + ', ' + dso.firstMetadataValue('person.givenName')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('person.email')" class="item-email card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span [innerHTML]="firstMetadataValue('person.email')"></span>
</ds-truncatable-part>
</p>
<p *ngIf="dso.hasMetadata('person.jobTitle')" class="item-jobtitle card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('person.jobTitle')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,47 @@
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { PersonGridElementComponent } from './person-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'person.email': [
{
language: 'en_US',
value: 'Smith-Donald@gmail.com'
}
],
'person.jobTitle': [
{
language: 'en_US',
value: 'Web Developer'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('PersonGridElementComponent', getEntityGridElementTestComponent(PersonGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['email', 'jobtitle']));

View File

@@ -0,0 +1,17 @@
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { Component } from '@angular/core';
import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
import { focusShadow } from '../../../../shared/animations/focus';
@rendersItemType('Person', ItemViewMode.Card)
@Component({
selector: 'ds-person-grid-element',
styleUrls: ['./person-grid-element.component.scss'],
templateUrl: './person-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Person
*/
export class PersonGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -0,0 +1,25 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata('dc.description')" class="item-description card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('dc.description')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,41 @@
import { ItemSearchResult } from '../../../../shared/object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { ProjectGridElementComponent } from './project-grid-element.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'dc.description': [
{
language: 'en_US',
value: 'The project description'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('ProjectGridElementComponent', getEntityGridElementTestComponent(ProjectGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['description']));

View File

@@ -0,0 +1,17 @@
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { Component } from '@angular/core';
import { focusShadow } from '../../../../shared/animations/focus';
import { TypedItemSearchResultGridElementComponent } from '../../../../shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
@rendersItemType('Project', ItemViewMode.Card)
@Component({
selector: 'ds-project-grid-element',
styleUrls: ['./project-grid-element.component.scss'],
templateUrl: './project-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Project
*/
export class ProjectGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -11,6 +11,9 @@ import { PersonMetadataListElementComponent } from './item-list-elements/person/
import { PersonListElementComponent } from './item-list-elements/person/person-list-element.component';
import { ProjectListElementComponent } from './item-list-elements/project/project-list-element.component';
import { TooltipModule } from 'ngx-bootstrap';
import { PersonGridElementComponent } from './item-grid-elements/person/person-grid-element.component';
import { OrgunitGridElementComponent } from './item-grid-elements/orgunit/orgunit-grid-element.component';
import { ProjectGridElementComponent } from './item-grid-elements/project/project-grid-element.component';
const ENTRY_COMPONENTS = [
OrgunitComponent,
@@ -20,7 +23,10 @@ const ENTRY_COMPONENTS = [
OrgUnitMetadataListElementComponent,
PersonListElementComponent,
PersonMetadataListElementComponent,
ProjectListElementComponent
ProjectListElementComponent,
PersonGridElementComponent,
OrgunitGridElementComponent,
ProjectGridElementComponent
];
@NgModule({

View File

@@ -9,6 +9,7 @@ import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynami
import { TranslateService } from '@ngx-translate/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models';
import { ResourceType } from '../../../core/shared/resource-type';
import { isNotEmpty } from '../../empty.util';
import { Community } from '../../../core/shared/community.model';
@@ -29,7 +30,7 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
/**
* Type of DSpaceObject that the form represents
*/
protected type;
protected type: ResourceType;
/**
* @type {string} Key prefix used to generate form labels
@@ -110,11 +111,11 @@ export class ComColFormComponent<T extends DSpaceObject> implements OnInit {
private updateFieldTranslations() {
this.formModel.forEach(
(fieldModel: DynamicInputModel) => {
fieldModel.label = this.translate.instant(this.type + this.LABEL_KEY_PREFIX + fieldModel.id);
fieldModel.label = this.translate.instant(this.type.value + this.LABEL_KEY_PREFIX + fieldModel.id);
if (isNotEmpty(fieldModel.validators)) {
fieldModel.errorMessages = {};
Object.keys(fieldModel.validators).forEach((key) => {
fieldModel.errorMessages[key] = this.translate.instant(this.type + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
fieldModel.errorMessages[key] = this.translate.instant(this.type.value + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
});
}
}

View File

@@ -15,7 +15,7 @@
<ng-container #componentViewContainer></ng-container>
<small *ngIf="hasHint && (!showErrorMessages || errorMessages.length === 0)"
class="text-muted" [innerHTML]="model.hint" [ngClass]="getClass('element', 'hint')"></small>
class="text-muted" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
<div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate:model.validators }}</small>

View File

@@ -3,6 +3,7 @@ import { MetadataRepresentationType } from '../../core/shared/metadata-represent
export enum ItemViewMode {
Element = 'element',
Card = 'card',
Full = 'full',
Metadata = 'metadata'
}

View File

@@ -1,4 +1,3 @@
<div class="thumbnail">
<img *ngIf="thumbnail && thumbnail.content" [src]="thumbnail.content" (error)="errorHandler($event)"/>
<img *ngIf="!(thumbnail && thumbnail.content)" [src]="holderSource | dsSafeUrl"/>
<img [src]="src | dsSafeUrl" (error)="errorHandler($event)"/>
</div>

View File

@@ -6,7 +6,7 @@ import { GridThumbnailComponent } from './grid-thumbnail.component';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { SafeUrlPipe } from '../../utils/safe-url-pipe';
describe('ThumbnailComponent', () => {
describe('GridThumbnailComponent', () => {
let comp: GridThumbnailComponent;
let fixture: ComponentFixture<GridThumbnailComponent>;
let de: DebugElement;
@@ -36,7 +36,7 @@ describe('ThumbnailComponent', () => {
it('should display placeholder', () => {
fixture.detectChanges();
const image: HTMLElement = de.query(By.css('img')).nativeElement;
expect(image.getAttribute('src')).toBe(comp.holderSource);
expect(image.getAttribute('src')).toBe(comp.defaultImage);
});
});

View File

@@ -1,5 +1,6 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { hasValue } from '../../empty.util';
/**
* This component renders a given Bitstream as a thumbnail.
@@ -12,7 +13,7 @@ import { Bitstream } from '../../../core/shared/bitstream.model';
styleUrls: ['./grid-thumbnail.component.scss'],
templateUrl: './grid-thumbnail.component.html'
})
export class GridThumbnailComponent {
export class GridThumbnailComponent implements OnInit {
@Input() thumbnail: Bitstream;
@@ -21,10 +22,19 @@ export class GridThumbnailComponent {
/**
* The default 'holder.js' image
*/
holderSource = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
@Input() defaultImage? = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+';
src: string;
errorHandler(event) {
event.currentTarget.src = this.holderSource;
event.currentTarget.src = this.defaultImage;
}
ngOnInit(): void {
if (hasValue(this.thumbnail) && this.thumbnail.content) {
this.src = this.thumbnail.content;
} else {
this.src = this.defaultImage
}
}
}

View File

@@ -0,0 +1,34 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-truncatable-part [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
class="item-authors card-text text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">
<span *ngIf="dso.hasMetadata('dc.date.issued')" class="item-date">{{dso.firstMetadataValue('dc.date.issued')}}</span>
<span *ngFor="let author of dso.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
<span [innerHTML]="author"></span>
</span>
</ds-truncatable-part>
</p>
<p *ngIf="dso.hasMetadata('dc.description.abstract')" class="item-abstract card-text">
<ds-truncatable-part [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('dc.description.abstract')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>

View File

@@ -0,0 +1,124 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TruncatePipe } from '../../../../utils/truncate.pipe';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { PublicationGridElementComponent } from './publication-grid-element.component';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
import { Item } from '../../../../../core/shared/item.model';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'dc.contributor.author': [
{
language: 'en_US',
value: 'Smith, Donald'
}
],
'dc.date.issued': [
{
language: null,
value: '2015-06-26'
}
],
'dc.description.abstract': [
{
language: 'en_US',
value: 'This is an abstract'
}
]
}
});
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('PublicationGridElementComponent', getEntityGridElementTestComponent(PublicationGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['authors', 'date', 'abstract']));
/**
* Create test cases for a grid component of an entity.
* @param component The component's class
* @param searchResultWithMetadata An ItemSearchResult containing an item with metadata that should be displayed in the grid element
* @param searchResultWithoutMetadata An ItemSearchResult containing an item that's missing the metadata that should be displayed in the grid element
* @param fieldsToCheck A list of fields to check. The tests expect to find html elements with class ".item-${field}", so make sure they exist in the html template of the grid element.
* For example: If one of the fields to check is labeled "authors", the html template should contain at least one element with class ".item-authors" that's
* present when the author metadata is available.
*/
export function getEntityGridElementTestComponent(component, searchResultWithMetadata: ItemSearchResult, searchResultWithoutMetadata: ItemSearchResult, fieldsToCheck: string[]) {
return () => {
let comp;
let fixture;
const truncatableServiceStub: any = {
isCollapsed: (id: number) => observableOf(true),
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [component, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub },
{provide: ITEM, useValue: searchResultWithoutMetadata}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(component, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(component);
comp = fixture.componentInstance;
}));
fieldsToCheck.forEach((field) => {
describe(`when the item has "${field}" metadata`, () => {
beforeEach(() => {
comp.dso = searchResultWithMetadata.indexableObject;
fixture.detectChanges();
});
it(`should show the "${field}" field`, () => {
const itemAuthorField = fixture.debugElement.query(By.css(`.item-${field}`));
expect(itemAuthorField).not.toBeNull();
});
});
describe(`when the item has no "${field}" metadata`, () => {
beforeEach(() => {
comp.dso = searchResultWithoutMetadata.indexableObject;
fixture.detectChanges();
});
it(`should not show the "${field}" field`, () => {
const itemAuthorField = fixture.debugElement.query(By.css(`.item-${field}`));
expect(itemAuthorField).toBeNull();
});
});
});
}
}

View File

@@ -0,0 +1,18 @@
import { TypedItemSearchResultGridElementComponent } from '../typed-item-search-result-grid-element.component';
import { DEFAULT_ITEM_TYPE, ItemViewMode, rendersItemType } from '../../../../items/item-type-decorator';
import { Component } from '@angular/core';
import { focusShadow } from '../../../../animations/focus';
@rendersItemType('Publication', ItemViewMode.Card)
@rendersItemType(DEFAULT_ITEM_TYPE, ItemViewMode.Card)
@Component({
selector: 'ds-publication-grid-element',
styleUrls: ['./publication-grid-element.component.scss'],
templateUrl: './publication-grid-element.component.html',
animations: [focusShadow]
})
/**
* The component for displaying a grid element for an item of the type Publication
*/
export class PublicationGridElementComponent extends TypedItemSearchResultGridElementComponent {
}

View File

@@ -0,0 +1,83 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TruncatePipe } from '../../../utils/truncate.pipe';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
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 { ITEM } from '../../../items/switcher/item-type-switcher.component';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { createRelationshipsObservable } from '../../../../+item-page/simple/item-types/shared/item.component.spec';
import { of as observableOf } from 'rxjs';
import { MetadataMap } from '../../../../core/shared/metadata.models';
import { TypedItemSearchResultGridElementComponent } from './typed-item-search-result-grid-element.component';
const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
metadata: [],
relationships: createRelationshipsObservable()
});
const mockSearchResult = {
indexableObject: mockItem as Item,
hitHighlights: new MetadataMap()
} as ItemSearchResult;
describe('TypedItemSearchResultGridElementComponent', () => {
let comp: TypedItemSearchResultGridElementComponent;
let fixture: ComponentFixture<TypedItemSearchResultGridElementComponent>;
describe('when injecting an Item', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TypedItemSearchResultGridElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: ITEM, useValue: mockItem}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(TypedItemSearchResultGridElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(TypedItemSearchResultGridElementComponent);
comp = fixture.componentInstance;
}));
it('should initiate item, object and dso correctly', () => {
expect(comp.item).toBe(mockItem);
expect(comp.dso).toBe(mockItem);
expect(comp.object.indexableObject).toBe(mockItem);
})
});
describe('when injecting an ItemSearchResult', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TypedItemSearchResultGridElementComponent, TruncatePipe],
providers: [
{provide: TruncatableService, useValue: {}},
{provide: ITEM, useValue: mockSearchResult}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(TypedItemSearchResultGridElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(TypedItemSearchResultGridElementComponent);
comp = fixture.componentInstance;
}));
it('should initiate item, object and dso correctly', () => {
expect(comp.item).toBe(mockItem);
expect(comp.dso).toBe(mockItem);
expect(comp.object.indexableObject).toBe(mockItem);
})
});
});

View File

@@ -0,0 +1,37 @@
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { Item } from '../../../../core/shared/item.model';
import { SearchResultGridElementComponent } from '../../search-result-grid-element/search-result-grid-element.component';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { Component, Inject } from '@angular/core';
import { ITEM } from '../../../items/switcher/item-type-switcher.component';
import { hasValue } from '../../../empty.util';
import { MetadataMap } from '../../../../core/shared/metadata.models';
/**
* A generic component for displaying item grid elements
*/
@Component({
selector: 'ds-item-search-result-grid-element',
template: ''
})
export class TypedItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {
item: Item;
constructor(
protected truncatableService: TruncatableService,
@Inject(ITEM) public obj: Item | ItemSearchResult,
) {
super(undefined, truncatableService);
if (hasValue((obj as any).indexableObject)) {
this.object = obj as ItemSearchResult;
this.dso = this.object.indexableObject;
} else {
this.object = {
indexableObject: obj as Item,
hitHighlights: new MetadataMap()
};
this.dso = obj as Item;
}
this.item = this.dso;
}
}

View File

@@ -4,6 +4,11 @@ ds-wrapper-grid-element ::ng-deep {
div.thumbnail > img {
height: $card-thumbnail-height;
width: 100%;
display: block;
min-width: 100%;
min-height: 100%;
object-fit: cover;
object-position: 50% 15%;
}
div.card {
margin-top: $ds-wrapper-grid-spacing;

View File

@@ -1,33 +1 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail()">
</ds-grid-thumbnail>
</div>
</a>
<div class="card-body">
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3" type="h4">
<h4 class="card-title" [innerHTML]="dso.firstMetadataValue('dc.title')"></h4>
</ds-truncatable-part>
<p *ngIf="dso.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
class="item-authors card-text text-muted">
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="1">
<span *ngIf="dso.hasMetadata('dc.date.issued')" class="item-date">{{dso.firstMetadataValue('dc.date.issued')}}</span>
<span *ngFor="let author of dso.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
<span [innerHTML]="author"></span>
</span>
</ds-truncatable-part>
</p>
<p class="item-abstract card-text">
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3">
<span [innerHTML]="firstMetadataValue('dc.description.abstract')"></span>
</ds-truncatable-part>
</p>
<div class="text-center">
<a [routerLink]="['/items/' + dso.id]"
class="lead btn btn-primary viewButton">View</a>
</div>
</div>
</div>
</ds-truncatable>
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>

View File

@@ -8,6 +8,7 @@ import { Item } from '../../../../core/shared/item.model';
import { TruncatableService } from '../../../truncatable/truncatable.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { ItemViewMode } from '../../../items/item-type-decorator';
let itemSearchResultGridElementComponent: ItemSearchResultGridElementComponent;
let fixture: ComponentFixture<ItemSearchResultGridElementComponent>;
@@ -16,41 +17,17 @@ const truncatableServiceStub: any = {
isCollapsed: (id: number) => observableOf(true),
};
const mockItemWithAuthorAndDate: ItemSearchResult = new ItemSearchResult();
mockItemWithAuthorAndDate.hitHighlights = {};
mockItemWithAuthorAndDate.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.contributor.author': [
{
language: 'en_US',
value: 'Smith, Donald'
}
],
'dc.date.issued': [
{
language: null,
value: '2015-06-26'
}
]
}
});
const type = 'authorOfPublication';
const mockItemWithoutAuthorAndDate: ItemSearchResult = new ItemSearchResult();
mockItemWithoutAuthorAndDate.hitHighlights = {};
mockItemWithoutAuthorAndDate.indexableObject = Object.assign(new Item(), {
const mockItemWithRelationshipType: ItemSearchResult = new ItemSearchResult();
mockItemWithRelationshipType.hitHighlights = {};
mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
'relationship.type': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'dc.type': [
{
language: null,
value: 'Article'
value: type
}
]
}
@@ -63,7 +40,7 @@ describe('ItemSearchResultGridElementComponent', () => {
declarations: [ItemSearchResultGridElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub },
{ provide: 'objectElementProvider', useValue: (mockItemWithoutAuthorAndDate) }
{ provide: 'objectElementProvider', useValue: (mockItemWithRelationshipType) }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemSearchResultGridElementComponent, {
@@ -76,51 +53,9 @@ describe('ItemSearchResultGridElementComponent', () => {
itemSearchResultGridElementComponent = fixture.componentInstance;
}));
describe('When the item has an author', () => {
beforeEach(() => {
itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.indexableObject;
fixture.detectChanges();
});
it('should show the author paragraph', () => {
const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
expect(itemAuthorField).not.toBeNull();
});
});
describe('When the item has no author', () => {
beforeEach(() => {
itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.indexableObject;
fixture.detectChanges();
});
it('should not show the author paragraph', () => {
const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
expect(itemAuthorField).toBeNull();
});
});
describe('When the item has an issuedate', () => {
beforeEach(() => {
itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.indexableObject;
fixture.detectChanges();
});
it('should show the issuedate span', () => {
const itemAuthorField = fixture.debugElement.query(By.css('span.item-date'));
expect(itemAuthorField).not.toBeNull();
});
});
describe('When the item has no issuedate', () => {
beforeEach(() => {
itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.indexableObject;
fixture.detectChanges();
});
it('should not show the issuedate span', () => {
const dateField = fixture.debugElement.query(By.css('span.item-date'));
expect(dateField).toBeNull();
});
it('should show send the object to item-type-switcher using viewMode "Card"', () => {
const itemTypeSwitcherComp = fixture.debugElement.query(By.css('ds-item-type-switcher')).componentInstance;
expect(itemTypeSwitcherComp.object).toBe(mockItemWithRelationshipType);
expect(itemTypeSwitcherComp.viewMode).toEqual(ItemViewMode.Card);
});
});

View File

@@ -6,6 +6,7 @@ import { Item } from '../../../../core/shared/item.model';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { SetViewMode } from '../../../view-mode';
import { focusShadow } from '../../../../shared/animations/focus';
import { ItemViewMode } from '../../../items/item-type-decorator';
@Component({
selector: 'ds-item-search-result-grid-element',
@@ -15,4 +16,6 @@ import { focusShadow } from '../../../../shared/animations/focus';
})
@renderElementsFor(ItemSearchResult, SetViewMode.Grid)
export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {}
export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> {
viewMode = ItemViewMode.Card;
}

View File

@@ -7,6 +7,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
import { TruncatableService } from '../../truncatable/truncatable.service';
import { Observable } from 'rxjs';
import { Metadata } from '../../../core/shared/metadata.utils';
import { hasValue } from '../../empty.util';
@Component({
selector: 'ds-search-result-grid-element',
@@ -16,9 +17,11 @@ import { Metadata } from '../../../core/shared/metadata.utils';
export class SearchResultGridElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends AbstractListableElementComponent<T> {
dso: K;
public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, private truncatableService: TruncatableService) {
public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, protected truncatableService: TruncatableService) {
super(listableObject);
this.dso = this.object.indexableObject;
if (hasValue(this.object)) {
this.dso = this.object.indexableObject;
}
}
/**

View File

@@ -24,7 +24,7 @@ const mockSearchResult = {
hitHighlights: new MetadataMap()
} as ItemSearchResult;
describe('ItemSearchResultComponent', () => {
describe('TypedItemSearchResultListElementComponent', () => {
let comp: TypedItemSearchResultListElementComponent;
let fixture: ComponentFixture<TypedItemSearchResultListElementComponent>;

View File

@@ -11,7 +11,7 @@ import { MetadataMap } from '../../../../core/shared/metadata.models';
* A generic component for displaying item list elements
*/
@Component({
selector: 'ds-item-search-result',
selector: 'ds-item-search-result-list-element',
template: ''
})
export class TypedItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {

View File

@@ -0,0 +1,3 @@
<div *ngIf="object && object.indexableObject && object.indexableObject.firstMetadataValue('relationship.type') as type">
<span class="badge badge-light">{{ type.toLowerCase() + '.listelement.badge' | translate }}</span>
</div>

View File

@@ -0,0 +1,83 @@
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
import { Item } from '../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { TruncatePipe } from '../../utils/truncate.pipe';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ItemTypeBadgeComponent } from './item-type-badge.component';
import { By } from '@angular/platform-browser';
let comp: ItemTypeBadgeComponent;
let fixture: ComponentFixture<ItemTypeBadgeComponent>;
const type = 'authorOfPublication';
const mockItemWithRelationshipType: ItemSearchResult = new ItemSearchResult();
mockItemWithRelationshipType.hitHighlights = {};
mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'relationship.type': [
{
language: 'en_US',
value: type
}
]
}
});
const mockItemWithoutRelationshipType: ItemSearchResult = new ItemSearchResult();
mockItemWithoutRelationshipType.hitHighlights = {};
mockItemWithoutRelationshipType.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('ItemTypeBadgeComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ItemTypeBadgeComponent, TruncatePipe],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemTypeBadgeComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemTypeBadgeComponent);
comp = fixture.componentInstance;
}));
describe('When the item has a relationship type', () => {
beforeEach(() => {
comp.object = mockItemWithRelationshipType;
fixture.detectChanges();
});
it('should show the relationship type badge', () => {
const badge = fixture.debugElement.query(By.css('span.badge'));
expect(badge.nativeElement.textContent).toContain(type.toLowerCase());
});
});
describe('When the item has no relationship type', () => {
beforeEach(() => {
comp.object = mockItemWithoutRelationshipType;
fixture.detectChanges();
});
it('should not show a badge', () => {
const badge = fixture.debugElement.query(By.css('span.badge'));
expect(badge).toBeNull();
});
});
});

View File

@@ -0,0 +1,12 @@
import { Component, Input } from '@angular/core';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import { SearchResult } from '../../../+search-page/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
@Component({
selector: 'ds-item-type-badge',
templateUrl: './item-type-badge.component.html'
})
export class ItemTypeBadgeComponent {
@Input() object: SearchResult<DSpaceObject>;
}

View File

@@ -1,4 +1,2 @@
<div *ngIf="object && object.indexableObject && object.indexableObject.firstMetadataValue('relationship.type') as type">
<span class="badge badge-light">{{ type.toLowerCase() + '.listelement.badge' | translate }}</span>
</div>
<ds-item-type-badge [object]="object"></ds-item-type-badge>
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>

View File

@@ -33,20 +33,6 @@ mockItemWithRelationshipType.indexableObject = Object.assign(new Item(), {
}
});
const mockItemWithoutRelationshipType: ItemSearchResult = new ItemSearchResult();
mockItemWithoutRelationshipType.hitHighlights = {};
mockItemWithoutRelationshipType.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
]
}
});
describe('ItemSearchResultListElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -54,7 +40,7 @@ describe('ItemSearchResultListElementComponent', () => {
declarations: [ItemSearchResultListElementComponent, TruncatePipe],
providers: [
{ provide: TruncatableService, useValue: truncatableServiceStub },
{ provide: 'objectElementProvider', useValue: (mockItemWithoutRelationshipType) }
{ provide: 'objectElementProvider', useValue: (mockItemWithRelationshipType) }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemSearchResultListElementComponent, {
@@ -67,27 +53,8 @@ describe('ItemSearchResultListElementComponent', () => {
itemSearchResultListElementComponent = fixture.componentInstance;
}));
describe('When the item has a relationship type', () => {
beforeEach(() => {
itemSearchResultListElementComponent.object = mockItemWithRelationshipType;
fixture.detectChanges();
});
it('should show the relationship type badge', () => {
const badge = fixture.debugElement.query(By.css('span.badge'));
expect(badge.nativeElement.textContent).toContain(type.toLowerCase());
});
});
describe('When the item has no relationship type', () => {
beforeEach(() => {
itemSearchResultListElementComponent.object = mockItemWithoutRelationshipType;
fixture.detectChanges();
});
it('should not show a badge', () => {
const badge = fixture.debugElement.query(By.css('span.badge'));
expect(badge).toBeNull();
});
it('should show a badge on top of the list element', () => {
const badge = fixture.debugElement.query(By.css('ds-item-type-badge')).componentInstance;
expect(badge.object).toBe(mockItemWithRelationshipType);
});
});

View File

@@ -1,9 +1,9 @@
<div *ngIf="currentPageState == undefined || currentPageState == currentPage">
<div class="pagination-masked clearfix top">
<div class="row">
<div *ngIf="!hidePaginationDetail" class="col-auto pagination-info">
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
<span class="align-middle" *ngIf="collectionSize">{{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}}</span>
<div *ngIf="!hidePaginationDetail && collectionSize > 0" class="col-auto pagination-info">
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
<span class="align-middle">{{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}}</span>
</div>
<div class="col">
<div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right">

View File

@@ -138,6 +138,9 @@ import { RoleDirective } from './roles/role.directive';
import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component';
import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component';
import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component';
import { TypedItemSearchResultGridElementComponent } from './object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component';
import { PublicationGridElementComponent } from './object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -256,8 +259,10 @@ const COMPONENTS = [
CollectionSearchResultListElementComponent,
ItemSearchResultListElementComponent,
TypedItemSearchResultListElementComponent,
TypedItemSearchResultGridElementComponent,
ItemTypeSwitcherComponent,
BrowseByComponent
BrowseByComponent,
ItemTypeBadgeComponent
];
const ENTRY_COMPONENTS = [
@@ -275,6 +280,7 @@ const ENTRY_COMPONENTS = [
CommunityGridElementComponent,
SearchResultGridElementComponent,
PublicationListElementComponent,
PublicationGridElementComponent,
BrowseEntryListElementComponent,
MyDSpaceResultDetailElementComponent,
SearchResultGridElementComponent,

View File

@@ -1,4 +1,4 @@
@import 'src/app/+item-page/simple/item-types/journal-issue/journal-issue.component.scss';
@import 'src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss';
:host {
> * {

View File

@@ -1,4 +1,4 @@
@import 'src/app/+item-page/simple/item-types/journal-volume/journal-volume.component.scss';
@import 'src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss';
:host {
> * {

View File

@@ -1,4 +1,4 @@
@import 'src/app/+item-page/simple/item-types/journal/journal.component.scss';
@import 'src/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss';
:host {
> * {

View File

@@ -1,4 +1,4 @@
@import 'src/app/+item-page/simple/item-types/orgunit/orgunit.component.scss';
@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss';
:host {
> * {

View File

@@ -1,4 +1,4 @@
@import 'src/app/+item-page/simple/item-types/person/person.component.scss';
@import 'src/app/entity-groups/research-entities/item-pages/person/person.component.scss';
:host {
> * {

View File

@@ -1,4 +1,4 @@
@import 'src/app/+item-page/simple/item-types/project/project.component.scss';
@import 'src/app/entity-groups/research-entities/item-pages/project/project.component.scss';
:host {
> * {