From 4461e9025be6cd43eda70a1f3602a7cf6511bc78 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 25 Jul 2019 17:46:23 +0200 Subject: [PATCH] 63945: Edit bitstream tab intermediate commit --- resources/i18n/en.json | 18 +++++ .../edit-item-page/edit-item-page.module.ts | 4 +- .../item-bitstreams.component.html | 70 ++++++++++++++++++- .../item-bitstreams.component.ts | 63 ++++++++++++++++- .../item-edit-bitstream.component.html | 23 ++++++ .../item-edit-bitstream.component.ts | 64 +++++++++++++++++ .../object-updates/object-updates.service.ts | 21 ++++++ src/app/core/shared/item-bitstreams-utils.ts | 50 +++++++++++++ 8 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html create mode 100644 src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts create mode 100644 src/app/core/shared/item-bitstreams-utils.ts 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> => + observableCombineLatest(source.pipe(toBitstreamsArray()), source.pipe(getBundleNames())).pipe( + map(([bitstreams, bundleNames]) => { + const bundleMap = new Map(); + bundleNames.forEach((bundleName: string) => { + bundleMap.set(bundleName, bitstreams.filter((bitstream: Bitstream) => bitstream.bundleName === bundleName)); + }); + return bundleMap; + }) + );