diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index a066ffe9d0..cc81394386 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -269,6 +269,24 @@
"content": "Your changes to this item's metadata were saved."
}
}
+ },
+ "bitstreams": {
+ "discard-button": "Discard",
+ "reinstate-button": "Undo",
+ "save-button": "Save",
+ "headers": {
+ "name": "Name",
+ "description": "Description",
+ "format": "Format",
+ "actions": "Actions",
+ "bundle": "Bundle"
+ },
+ "edit": {
+ "buttons": {
+ "remove": "Remove",
+ "undo": "Undo changes"
+ }
+ }
}
}
},
diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts
index 0c1de642ce..bb3bf3ecb3 100644
--- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts
+++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts
@@ -15,6 +15,7 @@ import { ItemDeleteComponent } from './item-delete/item-delete.component';
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component';
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
+import { ItemEditBitstreamComponent } from './item-bitstreams/item-edit-bitstream/item-edit-bitstream.component';
/**
* Module that contains all components related to the Edit Item page administrator functionality
@@ -38,7 +39,8 @@ import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.compo
ItemStatusComponent,
ItemMetadataComponent,
ItemBitstreamsComponent,
- EditInPlaceFieldComponent
+ EditInPlaceFieldComponent,
+ ItemEditBitstreamComponent
]
})
export class EditItemPageModule {
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
index b80e6e0678..77f22e12ea 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html
@@ -1,3 +1,71 @@
-
+
+
+
+
+
+
+
+
+
+ {{'item.edit.bitstreams.headers.name' | translate}} |
+ {{'item.edit.bitstreams.headers.description' | translate}} |
+ {{'item.edit.bitstreams.headers.format' | translate}} |
+ {{'item.edit.bitstreams.headers.actions' | translate}} |
+
+
+
+ {{'item.edit.bitstreams.headers.bundle' | translate}}: {{ updatesItem.key }} |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts
index 71f25cd5cf..1c347f6270 100644
--- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts
@@ -1,4 +1,11 @@
-import { Component } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
+import { switchMap, take } from 'rxjs/operators';
+import { Bitstream } from '../../../core/shared/bitstream.model';
+import { getBundleNames, toBitstreamsArray, toBundleMap } from '../../../core/shared/item-bitstreams-utils';
+import { Observable } from 'rxjs/internal/Observable';
+import { FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
+import { Subscription } from 'rxjs/internal/Subscription';
@Component({
selector: 'ds-item-bitstreams',
@@ -8,6 +15,56 @@ import { Component } from '@angular/core';
/**
* Component for displaying an item's bitstreams edit page
*/
-export class ItemBitstreamsComponent {
- /* TODO implement */
+export class ItemBitstreamsComponent extends AbstractItemUpdateComponent implements OnDestroy {
+
+ bundleNames$: Observable
;
+
+ updatesMap: Map>;
+
+ updatesMapSub: Subscription;
+
+ /**
+ * Set up and initialize all fields
+ */
+ ngOnInit(): void {
+ super.ngOnInit();
+ this.bundleNames$ = this.item.bitstreams.pipe(getBundleNames());
+ }
+
+ initializeNotificationsPrefix(): void {
+ this.notificationsPrefix = 'item.edit.bitstreams.notifications.';
+ }
+
+ initializeOriginalFields(): void {
+ this.item.bitstreams.pipe(
+ toBitstreamsArray(),
+ take(1)
+ ).subscribe((bitstreams: Bitstream[]) => {
+ this.objectUpdatesService.initialize(this.url, bitstreams, this.item.lastModified);
+ });
+ }
+
+ initializeUpdates(): void {
+ this.updates$ = this.item.bitstreams.pipe(
+ toBitstreamsArray(),
+ switchMap((bitstreams: Bitstream[]) => this.objectUpdatesService.getFieldUpdates(this.url, bitstreams))
+ );
+ this.updatesMapSub = this.item.bitstreams.pipe(
+ toBundleMap()
+ ).subscribe((bundleMap: Map) => {
+ const updatesMap = new Map();
+ bundleMap.forEach((bitstreams: Bitstream[], bundleName: string) => {
+ updatesMap.set(bundleName, this.objectUpdatesService.getFieldUpdatesExclusive(this.url, bitstreams));
+ });
+ this.updatesMap = updatesMap;
+ });
+ }
+
+ submit() {
+ // TODO: submit changes
+ }
+
+ ngOnDestroy(): void {
+ this.updatesMapSub.unsubscribe();
+ }
}
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html
new file mode 100644
index 0000000000..6a931df074
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html
@@ -0,0 +1,23 @@
+
+ {{ bitstream.name }}
+ |
+
+ {{ bitstream.description }}
+ |
+
+ {{ (format$ | async).shortDescription }}
+ |
+
+
+
+
+
+ |
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts
new file mode 100644
index 0000000000..dccfaea5d6
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts
@@ -0,0 +1,64 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
+import { Bitstream } from '../../../../core/shared/bitstream.model';
+import { cloneDeep } from 'lodash';
+import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
+import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
+import { Observable } from 'rxjs/internal/Observable';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
+
+@Component({
+ // tslint:disable-next-line:component-selector
+ selector: '[ds-item-edit-bitstream]',
+ templateUrl: './item-edit-bitstream.component.html',
+})
+export class ItemEditBitstreamComponent implements OnChanges {
+ @Input() fieldUpdate: FieldUpdate;
+
+ @Input() url: string;
+
+ bitstream: Bitstream;
+
+ format$: Observable;
+
+ constructor(private objectUpdatesService: ObjectUpdatesService) {
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.bitstream = cloneDeep(this.fieldUpdate.field) as Bitstream;
+ this.format$ = this.bitstream.format.pipe(
+ getSucceededRemoteData(),
+ getRemoteDataPayload()
+ );
+ }
+
+ /**
+ * Sends a new remove update for this field to the object updates service
+ */
+ remove(): void {
+ this.objectUpdatesService.saveRemoveFieldUpdate(this.url, this.bitstream);
+ }
+
+ /**
+ * Cancels the current update for this field in the object updates service
+ */
+ undo(): void {
+ this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.bitstream.uuid);
+ }
+
+ /**
+ * Check if a user should be allowed to remove this field
+ */
+ canRemove(): boolean {
+ return this.fieldUpdate.changeType !== FieldChangeType.REMOVE;
+ }
+
+ /**
+ * Check if a user should be allowed to cancel the update to this field
+ */
+ canUndo(): boolean {
+ return this.fieldUpdate.changeType >= 0;
+ }
+
+}
diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts
index 22d5fd3e77..08745f9223 100644
--- a/src/app/core/data/object-updates/object-updates.service.ts
+++ b/src/app/core/data/object-updates/object-updates.service.ts
@@ -105,6 +105,27 @@ export class ObjectUpdatesService {
}))
}
+ /**
+ * Method that combines the state's updates (excluding updates that aren't part of the initialFields) with
+ * the initial values (when there's no update) to create a FieldUpdates object
+ * @param url The URL of the page for which the FieldUpdates should be requested
+ * @param initialFields The initial values of the fields
+ */
+ getFieldUpdatesExclusive(url: string, initialFields: Identifiable[]): Observable {
+ const objectUpdates = this.getObjectEntry(url);
+ return objectUpdates.pipe(map((objectEntry) => {
+ const fieldUpdates: FieldUpdates = {};
+ for (const object of initialFields) {
+ let fieldUpdate = objectEntry.fieldUpdates[object.uuid];
+ if (isEmpty(fieldUpdate)) {
+ fieldUpdate = { field: object, changeType: undefined };
+ }
+ fieldUpdates[object.uuid] = fieldUpdate;
+ }
+ return fieldUpdates;
+ }))
+ }
+
/**
* Method to check if a specific field is currently editable in the store
* @param url The URL of the page on which the field resides
diff --git a/src/app/core/shared/item-bitstreams-utils.ts b/src/app/core/shared/item-bitstreams-utils.ts
new file mode 100644
index 0000000000..9d91b0ba76
--- /dev/null
+++ b/src/app/core/shared/item-bitstreams-utils.ts
@@ -0,0 +1,50 @@
+import { Observable } from 'rxjs/internal/Observable';
+import { RemoteData } from '../data/remote-data';
+import { PaginatedList } from '../data/paginated-list';
+import { Bitstream } from './bitstream.model';
+import { map } from 'rxjs/operators';
+import { combineLatest as observableCombineLatest } from 'rxjs';
+import { getSucceededRemoteData } from './operators';
+
+export const toBitstreamsArray = () =>
+ (source: Observable>>): Observable =>
+ source.pipe(
+ getSucceededRemoteData(),
+ map((bitstreamRD: RemoteData>) => bitstreamRD.payload.page)
+ );
+
+export const getBundleNames = () =>
+ (source: Observable>>): Observable =>
+ source.pipe(
+ toBitstreamsArray(),
+ map((bitstreams: Bitstream[]) => {
+ const result = [];
+ bitstreams.forEach((bitstream: Bitstream) => {
+ if (result.indexOf(bitstream.bundleName) < 0) {
+ result.push(bitstream.bundleName);
+ }
+ });
+ return result;
+ })
+ );
+
+export const filterByBundleName = (bundleName: string) =>
+ (source: Observable>>): Observable =>
+ source.pipe(
+ toBitstreamsArray(),
+ map((bitstreams: Bitstream[]) =>
+ bitstreams.filter((bitstream: Bitstream) => bitstream.bundleName === bundleName)
+ )
+ );
+
+export const toBundleMap = () =>
+ (source: Observable>>): Observable