Merge pull request #1438 from mspalti/iiif-bitstream-edit

Edit IIIF bitstream metadata
This commit is contained in:
Tim Donohue
2022-02-01 11:21:04 -06:00
committed by GitHub
5 changed files with 670 additions and 156 deletions

View File

@@ -12,7 +12,7 @@
</div>
</div>
</div>
<ds-form [formId]="'edit-bitstream-form-id'"
<ds-form *ngIf="formGroup" [formId]="'edit-bitstream-form-id'"
[formGroup]="formGroup"
[formModel]="formModel"
[formLayout]="formLayout"

View File

@@ -6,3 +6,7 @@
}
}
}
:host ::ng-deep ds-dynamic-form-control-container > div > label {
margin-top: 1.75rem;
}

View File

@@ -22,6 +22,8 @@ import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } f
import { getEntityEditRoute } from '../../item-page/item-page-routing-paths';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { Item } from '../../core/shared/item.model';
import { MetadataValueFilter } from '../../core/shared/metadata.models';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
@@ -31,24 +33,27 @@ let notificationsService: NotificationsService;
let formService: DynamicFormService;
let bitstreamService: BitstreamDataService;
let bitstreamFormatService: BitstreamFormatDataService;
let dsoNameService: DSONameService;
let bitstream: Bitstream;
let selectedFormat: BitstreamFormat;
let allFormats: BitstreamFormat[];
let router: Router;
describe('EditBitstreamPageComponent', () => {
let comp: EditBitstreamPageComponent;
let fixture: ComponentFixture<EditBitstreamPageComponent>;
let comp: EditBitstreamPageComponent;
let fixture: ComponentFixture<EditBitstreamPageComponent>;
beforeEach(waitForAsync(() => {
describe('EditBitstreamPageComponent', () => {
beforeEach(() => {
allFormats = [
Object.assign({
id: '1',
shortDescription: 'Unknown',
description: 'Unknown format',
supportLevel: BitstreamFormatSupportLevel.Unknown,
mimetype: 'application/octet-stream',
_links: {
self: { href: 'format-selflink-1' }
self: {href: 'format-selflink-1'}
}
}),
Object.assign({
@@ -56,8 +61,9 @@ describe('EditBitstreamPageComponent', () => {
shortDescription: 'PNG',
description: 'Portable Network Graphics',
supportLevel: BitstreamFormatSupportLevel.Known,
mimetype: 'image/png',
_links: {
self: { href: 'format-selflink-2' }
self: {href: 'format-selflink-2'}
}
}),
Object.assign({
@@ -65,19 +71,14 @@ describe('EditBitstreamPageComponent', () => {
shortDescription: 'GIF',
description: 'Graphics Interchange Format',
supportLevel: BitstreamFormatSupportLevel.Known,
mimetype: 'image/gif',
_links: {
self: { href: 'format-selflink-3' }
self: {href: 'format-selflink-3'}
}
})
] as BitstreamFormat[];
selectedFormat = allFormats[1];
notificationsService = jasmine.createSpyObj('notificationsService',
{
info: infoNotification,
warning: warningNotification,
success: successNotification
}
);
formService = Object.assign({
createFormGroup: (fModel: DynamicFormControlModel[]) => {
const controls = {};
@@ -90,156 +91,418 @@ describe('EditBitstreamPageComponent', () => {
return undefined;
}
});
bitstream = Object.assign(new Bitstream(), {
metadata: {
'dc.description': [
{
value: 'Bitstream description'
}
],
'dc.title': [
{
value: 'Bitstream title'
}
]
},
format: createSuccessfulRemoteDataObject$(selectedFormat),
_links: {
self: 'bitstream-selflink'
},
bundle: createSuccessfulRemoteDataObject$({
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
uuid: 'some-uuid'
}))
})
});
bitstreamService = jasmine.createSpyObj('bitstreamService', {
findById: createSuccessfulRemoteDataObject$(bitstream),
update: createSuccessfulRemoteDataObject$(bitstream),
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
commitUpdates: {},
patch: {}
});
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats))
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule],
declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective],
providers: [
{ provide: NotificationsService, useValue: notificationsService },
{ provide: DynamicFormService, useValue: formService },
{ provide: ActivatedRoute, useValue: { data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }), snapshot: { queryParams: {} } } },
{ provide: BitstreamDataService, useValue: bitstreamService },
{ provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
ChangeDetectorRef
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditBitstreamPageComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
router = TestBed.inject(Router);
spyOn(router, 'navigate');
notificationsService = jasmine.createSpyObj('notificationsService',
{
info: infoNotification,
warning: warningNotification,
success: successNotification
}
);
});
describe('on startup', () => {
let rawForm;
describe('EditBitstreamPageComponent no IIIF fields', () => {
beforeEach(waitForAsync(() => {
const bundleName = 'ORIGINAL';
bitstream = Object.assign(new Bitstream(), {
metadata: {
'dc.description': [
{
value: 'Bitstream description'
}
],
'dc.title': [
{
value: 'Bitstream title'
}
]
},
format: createSuccessfulRemoteDataObject$(selectedFormat),
_links: {
self: 'bitstream-selflink'
},
bundle: createSuccessfulRemoteDataObject$({
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
uuid: 'some-uuid',
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
return undefined;
},
}))
})
});
bitstreamService = jasmine.createSpyObj('bitstreamService', {
findById: createSuccessfulRemoteDataObject$(bitstream),
update: createSuccessfulRemoteDataObject$(bitstream),
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
commitUpdates: {},
patch: {}
});
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats))
});
dsoNameService = jasmine.createSpyObj('dsoNameService', {
getName: bundleName
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule],
declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective],
providers: [
{provide: NotificationsService, useValue: notificationsService},
{provide: DynamicFormService, useValue: formService},
{provide: ActivatedRoute,
useValue: {
data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}),
snapshot: {queryParams: {}}
}
},
{provide: BitstreamDataService, useValue: bitstreamService},
{provide: DSONameService, useValue: dsoNameService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
ChangeDetectorRef
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
rawForm = comp.formGroup.getRawValue();
fixture = TestBed.createComponent(EditBitstreamPageComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
router = TestBed.inject(Router);
spyOn(router, 'navigate');
});
it('should fill in the bitstream\'s title', () => {
expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(bitstream.name);
});
describe('on startup', () => {
let rawForm;
it('should fill in the bitstream\'s description', () => {
expect(rawForm.descriptionContainer.description).toEqual(bitstream.firstMetadataValue('dc.description'));
});
it('should select the correct format', () => {
expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.id);
});
it('should put the \"New Format\" input on invisible', () => {
expect(comp.formLayout.newFormat.grid.host).toContain('invisible');
});
});
describe('when an unknown format is selected', () => {
beforeEach(() => {
comp.updateNewFormatLayout(allFormats[0].id);
});
it('should remove the invisible class from the \"New Format\" input', () => {
expect(comp.formLayout.newFormat.grid.host).not.toContain('invisible');
});
});
describe('onSubmit', () => {
describe('when selected format hasn\'t changed', () => {
beforeEach(() => {
comp.onSubmit();
rawForm = comp.formGroup.getRawValue();
});
it('should call update', () => {
expect(bitstreamService.update).toHaveBeenCalled();
it('should fill in the bitstream\'s title', () => {
expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(bitstream.name);
});
it('should commit the updates', () => {
expect(bitstreamService.commitUpdates).toHaveBeenCalled();
it('should fill in the bitstream\'s description', () => {
expect(rawForm.descriptionContainer.description).toEqual(bitstream.firstMetadataValue('dc.description'));
});
it('should select the correct format', () => {
expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.id);
});
it('should put the \"New Format\" input on invisible', () => {
expect(comp.formLayout.newFormat.grid.host).toContain('invisible');
});
});
describe('when selected format has changed', () => {
describe('when an unknown format is selected', () => {
beforeEach(() => {
comp.formGroup.patchValue({
formatContainer: {
selectedFormat: allFormats[2].id
}
comp.updateNewFormatLayout(allFormats[0].id);
});
it('should remove the invisible class from the \"New Format\" input', () => {
expect(comp.formLayout.newFormat.grid.host).not.toContain('invisible');
});
});
describe('onSubmit', () => {
describe('when selected format hasn\'t changed', () => {
beforeEach(() => {
comp.onSubmit();
});
it('should call update', () => {
expect(bitstreamService.update).toHaveBeenCalled();
});
it('should commit the updates', () => {
expect(bitstreamService.commitUpdates).toHaveBeenCalled();
});
});
describe('when selected format has changed', () => {
beforeEach(() => {
comp.formGroup.patchValue({
formatContainer: {
selectedFormat: allFormats[2].id
}
});
fixture.detectChanges();
comp.onSubmit();
});
it('should call update', () => {
expect(bitstreamService.update).toHaveBeenCalled();
});
it('should call updateFormat', () => {
expect(bitstreamService.updateFormat).toHaveBeenCalled();
});
it('should commit the updates', () => {
expect(bitstreamService.commitUpdates).toHaveBeenCalled();
});
});
});
describe('when the cancel button is clicked', () => {
it('should call navigateToItemEditBitstreams method', () => {
spyOn(comp, 'navigateToItemEditBitstreams');
comp.onCancel();
expect(comp.navigateToItemEditBitstreams).toHaveBeenCalled();
});
});
describe('when navigateToItemEditBitstreams is called, and the component has an itemId', () => {
it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => {
comp.itemId = 'some-uuid1';
comp.navigateToItemEditBitstreams();
expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']);
});
});
describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => {
it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => {
comp.itemId = undefined;
comp.navigateToItemEditBitstreams();
expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']);
});
});
});
describe('EditBitstreamPageComponent with IIIF fields', () => {
const bundleName = 'ORIGINAL';
beforeEach(waitForAsync(() => {
bitstream = Object.assign(new Bitstream(), {
metadata: {
'dc.description': [
{
value: 'Bitstream description'
}
],
'dc.title': [
{
value: 'Bitstream title'
}
],
'iiif.label': [
{
value: 'chapter one'
}
],
'iiif.toc': [
{
value: 'chapter one'
}
],
'iiif.image.width': [
{
value: '2400'
}
],
'iiif.image.height': [
{
value: '2800'
}
],
},
format: createSuccessfulRemoteDataObject$(allFormats[1]),
_links: {
self: 'bitstream-selflink'
},
bundle: createSuccessfulRemoteDataObject$({
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
uuid: 'some-uuid',
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
return 'True';
}
}))
})
});
bitstreamService = jasmine.createSpyObj('bitstreamService', {
findById: createSuccessfulRemoteDataObject$(bitstream),
update: createSuccessfulRemoteDataObject$(bitstream),
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
commitUpdates: {},
patch: {}
});
dsoNameService = jasmine.createSpyObj('dsoNameService', {
getName: bundleName
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule],
declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective],
providers: [
{provide: NotificationsService, useValue: notificationsService},
{provide: DynamicFormService, useValue: formService},
{
provide: ActivatedRoute,
useValue: {
data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}),
snapshot: {queryParams: {}}
}
},
{provide: BitstreamDataService, useValue: bitstreamService},
{provide: DSONameService, useValue: dsoNameService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
ChangeDetectorRef
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditBitstreamPageComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
router = TestBed.inject(Router);
spyOn(router, 'navigate');
});
describe('on startup', () => {
let rawForm;
beforeEach(() => {
rawForm = comp.formGroup.getRawValue();
});
it('should set isIIIF to true', () => {
expect(comp.isIIIF).toBeTrue();
});
it('should fill in the iiif label', () => {
expect(rawForm.iiifLabelContainer.iiifLabel).toEqual('chapter one');
});
it('should fill in the iiif toc', () => {
expect(rawForm.iiifTocContainer.iiifToc).toEqual('chapter one');
});
it('should fill in the iiif width', () => {
expect(rawForm.iiifWidthContainer.iiifWidth).toEqual('2400');
});
it('should fill in the iiif height', () => {
expect(rawForm.iiifHeightContainer.iiifHeight).toEqual('2800');
});
});
});
describe('ignore OTHERCONTENT bundle', () => {
const bundleName = 'OTHERCONTENT';
beforeEach(waitForAsync(() => {
bitstream = Object.assign(new Bitstream(), {
metadata: {
'dc.description': [
{
value: 'Bitstream description'
}
],
'dc.title': [
{
value: 'Bitstream title'
}
],
'iiif.label': [
{
value: 'chapter one'
}
],
'iiif.toc': [
{
value: 'chapter one'
}
],
'iiif.image.width': [
{
value: '2400'
}
],
'iiif.image.height': [
{
value: '2800'
}
],
},
format: createSuccessfulRemoteDataObject$(allFormats[2]),
_links: {
self: 'bitstream-selflink'
},
bundle: createSuccessfulRemoteDataObject$({
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
uuid: 'some-uuid',
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
return 'True';
}
}))
})
});
bitstreamService = jasmine.createSpyObj('bitstreamService', {
findById: createSuccessfulRemoteDataObject$(bitstream),
update: createSuccessfulRemoteDataObject$(bitstream),
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
commitUpdates: {},
patch: {}
});
dsoNameService = jasmine.createSpyObj('dsoNameService', {
getName: bundleName
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule],
declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective],
providers: [
{provide: NotificationsService, useValue: notificationsService},
{provide: DynamicFormService, useValue: formService},
{provide: ActivatedRoute,
useValue: {
data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}),
snapshot: {queryParams: {}}
}
},
{provide: BitstreamDataService, useValue: bitstreamService},
{provide: DSONameService, useValue: dsoNameService},
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
ChangeDetectorRef
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditBitstreamPageComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
comp.onSubmit();
router = TestBed.inject(Router);
spyOn(router, 'navigate');
});
it('should call update', () => {
expect(bitstreamService.update).toHaveBeenCalled();
});
describe('EditBitstreamPageComponent with IIIF fields', () => {
let rawForm;
it('should call updateFormat', () => {
expect(bitstreamService.updateFormat).toHaveBeenCalled();
});
beforeEach(() => {
rawForm = comp.formGroup.getRawValue();
});
it('should commit the updates', () => {
expect(bitstreamService.commitUpdates).toHaveBeenCalled();
it('should NOT set isIIIF to true', () => {
expect(comp.isIIIF).toBeFalse();
});
it('should put the \"IIIF Label\" input not to be shown', () => {
expect(rawForm.iiifLabelContainer).toBeFalsy();
});
});
});
});
describe('when the cancel button is clicked', () => {
it('should call navigateToItemEditBitstreams method', () => {
spyOn(comp, 'navigateToItemEditBitstreams');
comp.onCancel();
expect(comp.navigateToItemEditBitstreams).toHaveBeenCalled();
});
});
describe('when navigateToItemEditBitstreams is called, and the component has an itemId', () => {
it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => {
comp.itemId = 'some-uuid1';
comp.navigateToItemEditBitstreams();
expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']);
});
});
describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => {
it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => {
comp.itemId = undefined;
comp.navigateToItemEditBitstreams();
expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']);
});
});
});

View File

@@ -1,16 +1,27 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnDestroy,
OnInit
} from '@angular/core';
import { Bitstream } from '../../core/shared/bitstream.model';
import { ActivatedRoute, Router } from '@angular/router';
import { map, mergeMap, switchMap } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import {
combineLatest,
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
Subscription
} from 'rxjs';
import {
DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormLayout,
DynamicFormService,
DynamicInputModel,
DynamicSelectModel,
DynamicTextAreaModel
DynamicSelectModel
} from '@ng-dynamic-forms/core';
import { FormGroup } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
@@ -28,13 +39,19 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { hasValue, isNotEmpty, isEmpty } from '../../shared/empty.util';
import { Metadata } from '../../core/shared/metadata.utils';
import { Location } from '@angular/common';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { getEntityEditRoute, getItemEditRoute } from '../../item-page/item-page-routing-paths';
import { Bundle } from '../../core/shared/bundle.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { Item } from '../../core/shared/item.model';
import {
DsDynamicInputModel
} from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
@Component({
selector: 'ds-edit-bitstream-page',
@@ -94,6 +111,26 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
*/
NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.';
/**
* IIIF image width metadata key
*/
IMAGE_WIDTH_METADATA = 'iiif.image.width';
/**
* IIIF image height metadata key
*/
IMAGE_HEIGHT_METADATA = 'iiif.image.height';
/**
* IIIF table of contents metadata key
*/
IIIF_TOC_METADATA = 'iiif.toc';
/**
* IIIF label metadata key
*/
IIIF_LABEL_METADATA = 'iiif.label';
/**
* Options for fetching all bitstream formats
*/
@@ -102,7 +139,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
/**
* The Dynamic Input Model for the file's name
*/
fileNameModel = new DynamicInputModel({
fileNameModel = new DsDynamicInputModel({
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
id: 'fileName',
name: 'fileName',
required: true,
@@ -118,14 +156,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
* The Dynamic Switch Model for the file's name
*/
primaryBitstreamModel = new DynamicCustomSwitchModel({
id: 'primaryBitstream',
name: 'primaryBitstream'
});
id: 'primaryBitstream',
name: 'primaryBitstream'
}
);
/**
* The Dynamic TextArea Model for the file's description
*/
descriptionModel = new DynamicTextAreaModel({
descriptionModel = new DsDynamicTextAreaModel({
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
id: 'description',
name: 'description',
rows: 10
@@ -147,10 +187,87 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
name: 'newFormat'
});
/**
* The Dynamic Input Model for the iiif label
*/
iiifLabelModel = new DsDynamicInputModel({
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
id: 'iiifLabel',
name: 'iiifLabel'
},
{
grid: {
host: 'col col-lg-6 d-inline-block'
}
});
iiifLabelContainer = new DynamicFormGroupModel({
id: 'iiifLabelContainer',
group: [this.iiifLabelModel]
},{
grid: {
host: 'form-row'
}
});
iiifTocModel = new DsDynamicInputModel({
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
id: 'iiifToc',
name: 'iiifToc',
},{
grid: {
host: 'col col-lg-6 d-inline-block'
}
});
iiifTocContainer = new DynamicFormGroupModel({
id: 'iiifTocContainer',
group: [this.iiifTocModel]
},{
grid: {
host: 'form-row'
}
});
iiifWidthModel = new DsDynamicInputModel({
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
id: 'iiifWidth',
name: 'iiifWidth',
},{
grid: {
host: 'col col-lg-6 d-inline-block'
}
});
iiifWidthContainer = new DynamicFormGroupModel({
id: 'iiifWidthContainer',
group: [this.iiifWidthModel]
},{
grid: {
host: 'form-row'
}
});
iiifHeightModel = new DsDynamicInputModel({
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
id: 'iiifHeight',
name: 'iiifHeight'
},{
grid: {
host: 'col col-lg-6 d-inline-block'
}
});
iiifHeightContainer = new DynamicFormGroupModel({
id: 'iiifHeightContainer',
group: [this.iiifHeightModel]
},{
grid: {
host: 'form-row'
}
});
/**
* All input models in a simple array for easier iterations
*/
inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel, this.newFormatModel];
inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel,
this.newFormatModel];
/**
* The dynamic form fields used for editing the information of a bitstream
@@ -163,7 +280,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
this.fileNameModel,
this.primaryBitstreamModel
]
}),
},{
grid: {
host: 'form-row'
}
}),
new DynamicFormGroupModel({
id: 'descriptionContainer',
group: [
@@ -254,18 +375,27 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
*/
entityType: string;
/**
* Set to true when the parent item supports IIIF.
*/
isIIIF = false;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = [];
constructor(private route: ActivatedRoute,
private router: Router,
private changeDetectorRef: ChangeDetectorRef,
private location: Location,
private formService: DynamicFormService,
private translate: TranslateService,
private bitstreamService: BitstreamDataService,
private dsoNameService: DSONameService,
private notificationsService: NotificationsService,
private bitstreamFormatService: BitstreamFormatDataService) {
}
@@ -277,7 +407,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
* - Translate the form labels and hints
*/
ngOnInit(): void {
this.formGroup = this.formService.createFormGroup(this.formModel);
this.itemId = this.route.snapshot.queryParams.itemId;
this.entityType = this.route.snapshot.queryParams.entityType;
@@ -301,13 +430,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
).subscribe(([bitstream, allFormats]) => {
this.bitstream = bitstream as Bitstream;
this.formats = allFormats.page;
this.updateFormatModel();
this.updateForm(this.bitstream);
this.setIiifStatus(this.bitstream);
})
);
this.updateFieldTranslations();
this.subs.push(
this.translate.onLangChange
.subscribe(() => {
@@ -316,6 +442,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
);
}
/**
* Initializes the form.
*/
setForm() {
this.formGroup = this.formService.createFormGroup(this.formModel);
this.updateFormatModel();
this.updateForm(this.bitstream);
this.updateFieldTranslations();
}
/**
* Update the current form values with bitstream properties
* @param bitstream
@@ -333,6 +469,22 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
newFormat: hasValue(bitstream.firstMetadata('dc.format')) ? bitstream.firstMetadata('dc.format').value : undefined
}
});
if (this.isIIIF) {
this.formGroup.patchValue({
iiifLabelContainer: {
iiifLabel: bitstream.firstMetadataValue(this.IIIF_LABEL_METADATA)
},
iiifTocContainer: {
iiifToc: bitstream.firstMetadataValue(this.IIIF_TOC_METADATA)
},
iiifWidthContainer: {
iiifWidth: bitstream.firstMetadataValue(this.IMAGE_WIDTH_METADATA)
},
iiifHeightContainer: {
iiifHeight: bitstream.firstMetadataValue(this.IMAGE_HEIGHT_METADATA)
}
});
}
this.bitstream.format.pipe(
getAllSucceededRemoteDataPayload()
).subscribe((format: BitstreamFormat) => {
@@ -467,6 +619,32 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
const primary = rawForm.fileNamePrimaryContainer.primaryBitstream;
Metadata.setFirstValue(newMetadata, 'dc.title', rawForm.fileNamePrimaryContainer.fileName);
Metadata.setFirstValue(newMetadata, 'dc.description', rawForm.descriptionContainer.description);
if (this.isIIIF) {
// It's helpful to remove these metadata elements entirely when the form value is empty.
// This avoids potential issues on the REST side and makes it possible to do things like
// remove an existing "table of contents" entry.
if (isEmpty(rawForm.iiifLabelContainer.iiifLabel)) {
delete newMetadata[this.IIIF_LABEL_METADATA];
} else {
Metadata.setFirstValue(newMetadata, this.IIIF_LABEL_METADATA, rawForm.iiifLabelContainer.iiifLabel);
}
if (isEmpty(rawForm.iiifTocContainer.iiifToc)) {
delete newMetadata[this.IIIF_TOC_METADATA];
} else {
Metadata.setFirstValue(newMetadata, this.IIIF_TOC_METADATA, rawForm.iiifTocContainer.iiifToc);
}
if (isEmpty(rawForm.iiifWidthContainer.iiifWidth)) {
delete newMetadata[this.IMAGE_WIDTH_METADATA];
} else {
Metadata.setFirstValue(newMetadata, this.IMAGE_WIDTH_METADATA, rawForm.iiifWidthContainer.iiifWidth);
}
if (isEmpty(rawForm.iiifHeightContainer.iiifHeight)) {
delete newMetadata[this.IMAGE_HEIGHT_METADATA];
} else {
Metadata.setFirstValue(newMetadata, this.IMAGE_HEIGHT_METADATA, rawForm.iiifHeightContainer.iiifHeight);
}
}
if (isNotEmpty(rawForm.formatContainer.newFormat)) {
Metadata.setFirstValue(newMetadata, 'dc.format', rawForm.formatContainer.newFormat);
}
@@ -497,6 +675,58 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
}
}
/**
* Verifies that the parent item is iiif-enabled. Checks bitstream mimetype to be
* sure it's an image, excluding bitstreams in the THUMBNAIL or OTHERCONTENT bundles.
* @param bitstream
*/
setIiifStatus(bitstream: Bitstream) {
const regexExcludeBundles = /OTHERCONTENT|THUMBNAIL|LICENSE/;
const regexIIIFItem = /true|yes/i;
const isImage$ = this.bitstream.format.pipe(
getFirstSucceededRemoteData(),
map((format: RemoteData<BitstreamFormat>) => format.payload.mimetype.includes('image/')));
const isIIIFBundle$ = this.bitstream.bundle.pipe(
getFirstSucceededRemoteData(),
map((bundle: RemoteData<Bundle>) =>
this.dsoNameService.getName(bundle.payload).match(regexExcludeBundles) == null));
const isEnabled$ = this.bitstream.bundle.pipe(
getFirstSucceededRemoteData(),
map((bundle: RemoteData<Bundle>) => bundle.payload.item.pipe(
getFirstSucceededRemoteData(),
map((item: RemoteData<Item>) =>
(item.payload.firstMetadataValue('dspace.iiif.enabled') &&
item.payload.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null)
))));
const iiifSub = combineLatest(
isImage$,
isIIIFBundle$,
isEnabled$
).subscribe(([isImage, isIIIFBundle, isEnabled]) => {
if (isImage && isIIIFBundle && isEnabled) {
this.isIIIF = true;
this.inputModels.push(this.iiifLabelModel);
this.formModel.push(this.iiifLabelContainer);
this.inputModels.push(this.iiifTocModel);
this.formModel.push(this.iiifTocContainer);
this.inputModels.push(this.iiifWidthModel);
this.formModel.push(this.iiifWidthContainer);
this.inputModels.push(this.iiifHeightModel);
this.formModel.push(this.iiifHeightContainer);
}
this.setForm();
this.changeDetectorRef.detectChanges();
});
this.subs.push(iiifSub);
}
/**
* Unsubscribe from open subscriptions
*/

View File

@@ -587,6 +587,23 @@
"bitstream.edit.notifications.error.format.title": "An error occurred saving the bitstream's format",
"bitstream.edit.form.iiifLabel.label": "IIIF Label",
"bitstream.edit.form.iiifLabel.hint": "Canvas label for this image. If not provided default label will be used.",
"bitstream.edit.form.iiifToc.label": "IIIF Table of Contents",
"bitstream.edit.form.iiifToc.hint": "Adding text here makes this the start of a new table of contents range.",
"bitstream.edit.form.iiifWidth.label": "IIIF Canvas Width",
"bitstream.edit.form.iiifWidth.hint": "The canvas width should usually match the image width.",
"bitstream.edit.form.iiifHeight.label": "IIIF Canvas Height",
"bitstream.edit.form.iiifHeight.hint": "The canvas height should usually match the image height.",
"bitstream.edit.notifications.saved.content": "Your changes to this bitstream were saved.",
"bitstream.edit.notifications.saved.title": "Bitstream saved",