diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index bca0c8e6b0..0d73638785 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -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",
@@ -117,6 +148,7 @@
"collection.item-mapper.tabs.browse": "Browse",
"collection.item-mapper.tabs.map": "Map",
"collection.page.browse.recent.head": "Recent Submissions",
+ "collection.page.browse.recent.empty": "No items to show",
"collection.page.license": "License",
"collection.page.news": "News",
"collection.select.confirm": "Confirm selected",
@@ -693,4 +725,4 @@
"uploader.or": ", or",
"uploader.processing": "Processing",
"uploader.queue-lenght": "Queue length"
-}
+}
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/admin-registries-routing.module.ts b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
index 8e3c322bc8..afdc46bf17 100644
--- a/src/app/+admin/admin-registries/admin-registries-routing.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
@@ -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'}
+ },
])
]
})
diff --git a/src/app/+admin/admin-registries/admin-registries.module.ts b/src/app/+admin/admin-registries/admin-registries.module.ts
index c7890e6697..bbeb59f0ab 100644
--- a/src/app/+admin/admin-registries/admin-registries.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries.module.ts
@@ -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
],
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html
new file mode 100644
index 0000000000..2b65b369b2
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
new file mode 100644
index 0000000000..0a10633956
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
@@ -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;
+
+ 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();
+
+ });
+ });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts
new file mode 100644
index 0000000000..9712be70ca
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts
@@ -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'));
+ }
+ }
+ );
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts
new file mode 100644
index 0000000000..58b0686dfd
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts
@@ -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
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts
new file mode 100644
index 0000000000..76576afc7a
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts
@@ -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
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts
new file mode 100644
index 0000000000..41880bf16c
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts
@@ -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;
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
new file mode 100644
index 0000000000..67f6aa373e
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
@@ -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 {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
index 1ac547653f..e5cf7cf5ec 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
@@ -2,13 +2,15 @@
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
index 3a680c906b..e672dc82ea 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
@@ -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;
- 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();
+ });
+ });
});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
index bc0cbb8da6..cb7aa1ef91 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
@@ -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>>;
+ /**
+ * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats
+ */
+ pageState: BehaviorSubject;
+
+ /**
+ * 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 {
+ 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);
}
}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts
new file mode 100644
index 0000000000..0800c50169
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts
@@ -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 {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts
new file mode 100644
index 0000000000..f6eef741fd
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts
@@ -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> {
+ 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<> 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> {
+ return this.bitstreamFormatDataService.findById(route.params.id)
+ .pipe(
+ find((RD) => hasValue(RD.error) || RD.hasSucceeded),
+ );
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html
new file mode 100644
index 0000000000..f57ec9cd38
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
new file mode 100644
index 0000000000..cfa93a15a8
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
@@ -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;
+
+ 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) => {
+ 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();
+
+ });
+ });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts
new file mode 100644
index 0000000000..0fdcc75689
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts
@@ -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>;
+
+ 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)
+ );
+ }
+
+ /**
+ * 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');
+ }
+ }
+ );
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html
new file mode 100644
index 0000000000..be6ebf2599
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
new file mode 100644
index 0000000000..2870705fc8
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
@@ -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;
+
+ 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']);
+ });
+ });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
new file mode 100644
index 0000000000..505ccccd91
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
@@ -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 = new EventEmitter();
+
+ /**
+ * 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()]);
+ }
+}
diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts
index 71af51c683..2003ecf124 100644
--- a/src/app/+admin/admin-routing.module.ts
+++ b/src/app/+admin/admin-routing.module.ts
@@ -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'
}
])
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html
index 91239de17c..2b16bc1ca6 100644
--- a/src/app/+collection-page/collection-page.component.html
+++ b/src/app/+collection-page/collection-page.component.html
@@ -52,6 +52,9 @@
message="{{'error.recent-submissions' | translate}}">
+
+ {{'collection.page.browse.recent.empty' | translate}}
+
= {
hostWindow: hostWindowReducer,
forms: formReducer,
metadataRegistry: metadataRegistryReducer,
+ bitstreamFormats: bitstreamFormatReducer,
notifications: notificationsReducer,
searchSidebar: sidebarReducer,
searchFilter: filterReducer,
diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts
index 5ee135b530..2283ecb368 100644
--- a/src/app/core/cache/models/normalized-bitstream-format.model.ts
+++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts
@@ -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
* 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
* String representing this Bitstream Format's file extension
*/
@autoserialize
- extensions: string;
+ extensions: string[];
/**
* Identifier for this Bitstream Format
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 08f09d99cc..62e9ca5011 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -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';
@@ -155,6 +156,7 @@ const PROVIDERS = [
PaginationComponentOptions,
ResourcePolicyService,
RegistryService,
+ BitstreamFormatDataService,
NormalizedObjectBuildService,
RemoteDataBuildService,
RequestService,
diff --git a/src/app/core/data/bitstream-format-data.service.spec.ts b/src/app/core/data/bitstream-format-data.service.spec.ts
new file mode 100644
index 0000000000..f3ce478236
--- /dev/null
+++ b/src/app/core/data/bitstream-format-data.service.spec.ts
@@ -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;
+
+ const objectCache = {} as ObjectCacheService;
+ const halEndpointService = {
+ getEndpoint(linkPath: string): Observable {
+ 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 {
+ 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 {
+ 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);
+ });
+ });
+});
diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts
new file mode 100644
index 0000000000..a5638183c0
--- /dev/null
+++ b/src/app/core/data/bitstream-format-data.service.ts
@@ -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 {
+
+ protected linkPath = 'bitstreamformats';
+ protected forceBypassCache = false;
+
+ constructor(
+ protected requestService: RequestService,
+ protected rdbService: RemoteDataBuildService,
+ protected dataBuildService: NormalizedObjectBuildService,
+ protected store: Store,
+ protected objectCache: ObjectCacheService,
+ protected halService: HALEndpointService,
+ protected notificationsService: NotificationsService,
+ protected http: HttpClient,
+ protected comparator: DefaultChangeAnalyzer) {
+ super();
+ }
+
+ /**
+ * Get the endpoint for browsing bitstream formats
+ * @param {FindAllOptions} options
+ * @returns {Observable}
+ */
+ getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable {
+ return this.halService.getEndpoint(this.linkPath);
+ }
+
+ /**
+ * Get the endpoint to update an existing bitstream format
+ * @param formatId
+ */
+ public getUpdateEndpoint(formatId: string): Observable {
+ return this.getBrowseEndpoint().pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, formatId))
+ );
+ }
+
+ /**
+ * Get the endpoint to create a new bitstream format
+ */
+ public getCreateEndpoint(): Observable {
+ return this.getBrowseEndpoint();
+ }
+
+ /**
+ * Update an existing bitstreamFormat
+ * @param bitstreamFormat
+ */
+ updateBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable {
+ 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 {
+ 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 {
+ return this.getBrowseEndpoint().pipe(
+ tap((href: string) => this.requestService.removeByHrefSubstring(href))
+ );
+ }
+
+ /**
+ * Gets all the selected BitstreamFormats from the store
+ */
+ public getSelectedBitstreamFormats(): Observable {
+ 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 {
+ 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)
+ );
+ }
+}
diff --git a/src/app/core/registry/mock-bitstream-format.model.ts b/src/app/core/registry/mock-bitstream-format.model.ts
deleted file mode 100644
index f5811e367c..0000000000
--- a/src/app/core/registry/mock-bitstream-format.model.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export class BitstreamFormat {
- shortDescription: string;
- description: string;
- mimetype: string;
- supportLevel: number;
- internal: boolean;
- extensions: string;
-}
diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts
index 47e306d624..455a8043da 100644
--- a/src/app/core/registry/registry.service.spec.ts
+++ b/src/app/core/registry/registry.service.spec.ts
@@ -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, payloadObs: Observable) => {
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', () => {
diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts
index d816c5eab8..206426588e 100644
--- a/src/app/core/registry/registry.service.ts
+++ b/src/app/core/registry/registry.service.ts
@@ -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>> {
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>> {
- const requestObs = this.getBitstreamFormatsRequestObs(pagination);
-
- const requestEntryObs = requestObs.pipe(
- flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
- );
-
- const rbrObs: Observable = requestEntryObs.pipe(
- getResponseFromEntry(),
- map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse)
- );
-
- const bitstreamformatsObs: Observable = rbrObs.pipe(
- map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats)
- );
-
- const pageInfoObs: Observable = 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 {
+ public getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable {
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 {
- 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 {
- 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 {
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 {
return this.halService.getEndpoint(this.metadataFieldsPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
- )
+ );
}
private delete(path: string, id: number): Observable {
@@ -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);
}
});
}
diff --git a/src/app/core/shared/bitstream-format-support-level.ts b/src/app/core/shared/bitstream-format-support-level.ts
new file mode 100644
index 0000000000..d92aac7708
--- /dev/null
+++ b/src/app/core/shared/bitstream-format-support-level.ts
@@ -0,0 +1,5 @@
+export enum BitstreamFormatSupportLevel {
+ Known = 'KNOWN',
+ Unknown = 'UNKNOWN',
+ Supported = 'SUPPORTED'
+}
diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts
index bf50cd832f..0e1279e978 100644
--- a/src/app/core/shared/bitstream-format.model.ts
+++ b/src/app/core/shared/bitstream-format.model.ts
@@ -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;
+
}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html
new file mode 100644
index 0000000000..4cb34a140b
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts
new file mode 100644
index 0000000000..68a05b66c3
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts
@@ -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']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts
new file mode 100644
index 0000000000..06c27ebacf
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html
new file mode 100644
index 0000000000..d7c9b68a24
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts
new file mode 100644
index 0000000000..5a8fca5fc6
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts
@@ -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']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts
new file mode 100644
index 0000000000..e5183536ef
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html
new file mode 100644
index 0000000000..467cdd1594
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dso.firstMetadataValue('creativework.editor')}}
+
+ ,
+ {{dso.firstMetadataValue('creativework.publisher')}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts
new file mode 100644
index 0000000000..8c12c1a266
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts
@@ -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']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts
new file mode 100644
index 0000000000..7f23211538
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/entity-groups/journal-entities/journal-entities.module.ts b/src/app/entity-groups/journal-entities/journal-entities.module.ts
index 50ec160650..4033645e1b 100644
--- a/src/app/entity-groups/journal-entities/journal-entities.module.ts
+++ b/src/app/entity-groups/journal-entities/journal-entities.module.ts
@@ -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({
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html
new file mode 100644
index 0000000000..104d3a0a57
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dso.firstMetadataValue('organization.address.addressCountry')}}
+
+ ,
+ {{dso.firstMetadataValue('organization.address.addressLocality')}}
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts
new file mode 100644
index 0000000000..39ddea4c7b
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.spec.ts
@@ -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']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts
new file mode 100644
index 0000000000..0effc22027
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html
new file mode 100644
index 0000000000..86353377fa
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts
new file mode 100644
index 0000000000..a0f8e4c29e
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts
@@ -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']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts
new file mode 100644
index 0000000000..bf7b8aa119
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html
new file mode 100644
index 0000000000..a595791cc4
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html
@@ -0,0 +1,25 @@
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts
new file mode 100644
index 0000000000..9ad26935b7
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts
@@ -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']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts
new file mode 100644
index 0000000000..15d525fcf2
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts
index ba28f174df..099fa2a6a3 100644
--- a/src/app/entity-groups/research-entities/research-entities.module.ts
+++ b/src/app/entity-groups/research-entities/research-entities.module.ts
@@ -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({
diff --git a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
index 6a96892b06..8d1d5c1dca 100644
--- a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
+++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
@@ -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 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 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);
});
}
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
index cead04f797..217f9e79cf 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
@@ -14,7 +14,7 @@
-
+
{{ message | translate:model.validators }}
diff --git a/src/app/shared/items/item-type-decorator.ts b/src/app/shared/items/item-type-decorator.ts
index 2420e71908..3a040ae5bf 100644
--- a/src/app/shared/items/item-type-decorator.ts
+++ b/src/app/shared/items/item-type-decorator.ts
@@ -3,6 +3,7 @@ import { MetadataRepresentationType } from '../../core/shared/metadata-represent
export enum ItemViewMode {
Element = 'element',
+ Card = 'card',
Full = 'full',
Metadata = 'metadata'
}
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html
index c0c3c1f65f..5b09d09a55 100644
--- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html
+++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html
@@ -1,4 +1,3 @@
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts
index 2d2bd6305a..170ca34b42 100644
--- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts
+++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts
@@ -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
;
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);
});
});
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts
index 8ca93470da..6ae0c2d37e 100644
--- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts
+++ b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts
@@ -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
+ }
}
}
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html
new file mode 100644
index 0000000000..e2477524ca
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{dso.firstMetadataValue('dc.date.issued')}}
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts
new file mode 100644
index 0000000000..f067a21ae0
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec.ts
@@ -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();
+ });
+ });
+ });
+ }
+}
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts
new file mode 100644
index 0000000000..1bcd028baf
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.ts
@@ -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 {
+}
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..e4ace8d0b2
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.spec.ts
@@ -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;
+
+ 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);
+ })
+ });
+});
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..f4f470c052
--- /dev/null
+++ b/src/app/shared/object-grid/item-grid-element/item-types/typed-item-search-result-grid-element.component.ts
@@ -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 {
+ 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;
+ }
+}
diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss
index 437dfc3b43..8f19309d89 100644
--- a/src/app/shared/object-grid/object-grid.component.scss
+++ b/src/app/shared/object-grid/object-grid.component.scss
@@ -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;
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
index c7e2f524f3..d433c7acf2 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
@@ -1,33 +1 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{dso.firstMetadataValue('dc.date.issued')}}
- ,
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts
index 655fd268a7..282478ec33 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.spec.ts
@@ -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;
@@ -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);
});
});
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts
index 30c36b3af9..7bbe41fe60 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.ts
@@ -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 {}
+export class ItemSearchResultGridElementComponent extends SearchResultGridElementComponent {
+ viewMode = ItemViewMode.Card;
+}
diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts
index 0961dc96ee..5f31d52ae7 100644
--- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts
@@ -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, K extends DSpaceObject> extends AbstractListableElementComponent {
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;
+ }
}
/**
diff --git a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts
index 9adf255523..082347be0b 100644
--- a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts
+++ b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.spec.ts
@@ -24,7 +24,7 @@ const mockSearchResult = {
hitHighlights: new MetadataMap()
} as ItemSearchResult;
-describe('ItemSearchResultComponent', () => {
+describe('TypedItemSearchResultListElementComponent', () => {
let comp: TypedItemSearchResultListElementComponent;
let fixture: ComponentFixture;
diff --git a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts
index 7df3ab5681..dd1b5a7e5f 100644
--- a/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts
+++ b/src/app/shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component.ts
@@ -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 {
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.html b/src/app/shared/object-list/item-type-badge/item-type-badge.component.html
new file mode 100644
index 0000000000..35d7663801
--- /dev/null
+++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.html
@@ -0,0 +1,3 @@
+
+ {{ type.toLowerCase() + '.listelement.badge' | translate }}
+
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts
new file mode 100644
index 0000000000..04c40b73ff
--- /dev/null
+++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.spec.ts
@@ -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;
+
+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();
+ });
+ });
+});
diff --git a/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts
new file mode 100644
index 0000000000..9ffba33758
--- /dev/null
+++ b/src/app/shared/object-list/item-type-badge/item-type-badge.component.ts
@@ -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;
+}
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html
index a2617a956f..051a27bde7 100644
--- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html
+++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.html
@@ -1,4 +1,2 @@
-
- {{ type.toLowerCase() + '.listelement.badge' | translate }}
-
+
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts
index a370d3a632..8f41018404 100644
--- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts
+++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.spec.ts
@@ -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);
});
});
diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html
index 22a58dd7fc..c16a153026 100644
--- a/src/app/shared/pagination/pagination.component.html
+++ b/src/app/shared/pagination/pagination.component.html
@@ -1,9 +1,9 @@