diff --git a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..255532c8c4d0ee426aaccda5f79e7a82a53df774 --- /dev/null +++ b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts @@ -0,0 +1,120 @@ +import { Inject, Injectable, OnInit } from '@angular/core'; +import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; +import { Observable } from 'rxjs/internal/Observable'; +import { Item } from '../../../core/shared/item.model'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; +import { first, map } from 'rxjs/operators'; +import { RemoteData } from '../../../core/data/remote-data'; +import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component'; + +@Injectable() +/** + * Abstract component for managing object updates of an item + */ +export abstract class AbstractItemUpdateComponent extends AbstractTrackableComponent implements OnInit { + /** + * The item to display the edit page for + */ + item: Item; + /** + * The current values and updates for all this item's fields + * Should be initialized in the initializeUpdates method of the child component + */ + updates$: Observable<FieldUpdates>; + + constructor( + public itemService: ItemDataService, + public objectUpdatesService: ObjectUpdatesService, + public router: Router, + public notificationsService: NotificationsService, + public translateService: TranslateService, + @Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig, + public route: ActivatedRoute + ) { + super(objectUpdatesService, notificationsService, translateService) + } + + /** + * Initialize common properties between item-update components + */ + ngOnInit(): void { + this.route.parent.data.pipe(map((data) => data.item)) + .pipe( + first(), + map((data: RemoteData<Item>) => data.payload) + ).subscribe((item: Item) => { + this.item = item; + }); + + this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout; + this.url = this.router.url; + if (this.url.indexOf('?') > 0) { + this.url = this.url.substr(0, this.url.indexOf('?')); + } + this.hasChanges().pipe(first()).subscribe((hasChanges) => { + if (!hasChanges) { + this.initializeOriginalFields(); + } else { + this.checkLastModified(); + } + }); + + this.initializeNotificationsPrefix(); + this.initializeUpdates(); + } + + /** + * Initialize the values and updates of the current item's fields + */ + abstract initializeUpdates(): void; + + /** + * Initialize the prefix for notification messages + */ + abstract initializeNotificationsPrefix(): void; + + /** + * Sends all initial values of this item to the object updates service + */ + abstract initializeOriginalFields(): void; + + /** + * Prevent unnecessary rerendering so fields don't lose focus + */ + trackUpdate(index, update: FieldUpdate) { + return update && update.field ? update.field.uuid : undefined; + } + + /** + * Check if the current page is entirely valid + */ + protected isValid() { + return this.objectUpdatesService.isValidPage(this.url); + } + + /** + * Checks if the current item is still in sync with the version in the store + * If it's not, a notification is shown and the changes are removed + */ + private checkLastModified() { + const currentVersion = this.item.lastModified; + this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( + (updateVersion: Date) => { + if (updateVersion.getDate() !== currentVersion.getDate()) { + this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); + this.initializeOriginalFields(); + } + } + ); + } + + /** + * Submit the current changes + */ + abstract submit(): void; +} diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts index 6b3e05c818bfa6a795cac2c7543c7d9ca611f3f6..9b1d2c96486855d833c7d8b04bc98c8af850c30f 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnInit } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { ItemDataService } from '../../../core/data/item-data.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; @@ -6,8 +6,6 @@ import { ActivatedRoute, Router } from '@angular/router'; import { cloneDeep } from 'lodash'; import { Observable } from 'rxjs'; import { - FieldUpdate, - FieldUpdates, Identifiable } from '../../../core/data/object-updates/object-updates.reducer'; import { first, map, switchMap, take, tap } from 'rxjs/operators'; @@ -20,6 +18,7 @@ import { RegistryService } from '../../../core/registry/registry.service'; import { MetadataField } from '../../../core/metadata/metadatafield.model'; import { MetadatumViewModel } from '../../../core/shared/metadata.models'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; @Component({ selector: 'ds-item-metadata', @@ -29,28 +28,7 @@ import { Metadata } from '../../../core/shared/metadata.utils'; /** * Component for displaying an item's metadata edit page */ -export class ItemMetadataComponent implements OnInit { - - /** - * The item to display the edit page for - */ - item: Item; - /** - * The current values and updates for all this item's metadata fields - */ - updates$: Observable<FieldUpdates>; - /** - * The current url of this page - */ - url: string; - /** - * The time span for being able to undo discarding changes - */ - private discardTimeOut: number; - /** - * Prefix for this component's notification translate keys - */ - private notificationsPrefix = 'item.edit.metadata.notifications.'; +export class ItemMetadataComponent extends AbstractItemUpdateComponent { /** * Observable with a list of strings with all existing metadata field keys @@ -58,90 +36,60 @@ export class ItemMetadataComponent implements OnInit { metadataFields$: Observable<string[]>; constructor( - private itemService: ItemDataService, - private objectUpdatesService: ObjectUpdatesService, - private router: Router, - private notificationsService: NotificationsService, - private translateService: TranslateService, - @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, - private route: ActivatedRoute, - private metadataFieldService: RegistryService, + public itemService: ItemDataService, + public objectUpdatesService: ObjectUpdatesService, + public router: Router, + public notificationsService: NotificationsService, + public translateService: TranslateService, + @Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig, + public route: ActivatedRoute, + public metadataFieldService: RegistryService, ) { - + super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } /** * Set up and initialize all fields */ ngOnInit(): void { + super.ngOnInit(); this.metadataFields$ = this.findMetadataFields(); - this.route.parent.data.pipe(map((data) => data.item)) - .pipe( - first(), - map((data: RemoteData<Item>) => data.payload) - ).subscribe((item: Item) => { - this.item = item; - }); - - this.discardTimeOut = this.EnvConfig.item.edit.undoTimeout; - this.url = this.router.url; - if (this.url.indexOf('?') > 0) { - this.url = this.url.substr(0, this.url.indexOf('?')); - } - this.hasChanges().pipe(first()).subscribe((hasChanges) => { - if (!hasChanges) { - this.initializeOriginalFields(); - } else { - this.checkLastModified(); - } - }); - this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); } /** - * Sends a new add update for a field to the object updates service - * @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum + * Initialize the values and updates of the current item's metadata fields */ - add(metadata: MetadatumViewModel = new MetadatumViewModel()) { - this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata); - - } + public initializeUpdates(): void { + this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); + } /** - * Request the object updates service to discard all current changes to this item - * Shows a notification to remind the user that they can undo this + * Initialize the prefix for notification messages */ - discard() { - const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut }); - this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification); + public initializeNotificationsPrefix(): void { + this.notificationsPrefix = 'item.edit.metadata.notifications.'; } /** - * Request the object updates service to undo discarding all changes to this item + * Sends a new add update for a field to the object updates service + * @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum */ - reinstate() { - this.objectUpdatesService.reinstateFieldUpdates(this.url); + add(metadata: MetadatumViewModel = new MetadatumViewModel()) { + this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata); } /** * Sends all initial values of this item to the object updates service */ - private initializeOriginalFields() { + public initializeOriginalFields() { this.objectUpdatesService.initialize(this.url, this.item.metadataAsList, this.item.lastModified); } - /** - * Prevent unnecessary rerendering so fields don't lose focus - */ - trackUpdate(index, update: FieldUpdate) { - return update && update.field ? update.field.uuid : undefined; - } - /** * Requests all current metadata for this item and requests the item service to update the item * Makes sure the new version of the item is rendered on the page */ - submit() { + public submit() { this.isValid().pipe(first()).subscribe((isValid) => { if (isValid) { const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadataAsList) as Observable<MetadatumViewModel[]>; @@ -167,60 +115,6 @@ export class ItemMetadataComponent implements OnInit { }); } - /** - * Checks whether or not there are currently updates for this item - */ - hasChanges(): Observable<boolean> { - return this.objectUpdatesService.hasUpdates(this.url); - } - - /** - * Checks whether or not the item is currently reinstatable - */ - isReinstatable(): Observable<boolean> { - return this.objectUpdatesService.isReinstatable(this.url); - } - - /** - * Checks if the current item is still in sync with the version in the store - * If it's not, a notification is shown and the changes are removed - */ - private checkLastModified() { - const currentVersion = this.item.lastModified; - this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( - (updateVersion: Date) => { - if (updateVersion.getDate() !== currentVersion.getDate()) { - this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); - this.initializeOriginalFields(); - } - } - ); - } - - /** - * Check if the current page is entirely valid - */ - private isValid() { - return this.objectUpdatesService.isValidPage(this.url); - } - - /** - * Get translated notification title - * @param key - */ - private getNotificationTitle(key: string) { - return this.translateService.instant(this.notificationsPrefix + key + '.title'); - } - - /** - * Get translated notification content - * @param key - */ - private getNotificationContent(key: string) { - return this.translateService.instant(this.notificationsPrefix + key + '.content'); - - } - /** * Method to request all metadata fields and convert them to a list of strings */ diff --git a/src/app/shared/trackable/abstract-trackable.component.ts b/src/app/shared/trackable/abstract-trackable.component.ts index cd1b425f1058387864523fd35747aac4aa257340..e1a99d90b9422b5229aaaade17557d57fa7acccf 100644 --- a/src/app/shared/trackable/abstract-trackable.component.ts +++ b/src/app/shared/trackable/abstract-trackable.component.ts @@ -63,7 +63,7 @@ export class AbstractTrackableComponent { * Get translated notification title * @param key */ - private getNotificationTitle(key: string) { + getNotificationTitle(key: string) { return this.translateService.instant(this.notificationsPrefix + key + '.title'); } @@ -71,7 +71,7 @@ export class AbstractTrackableComponent { * Get translated notification content * @param key */ - private getNotificationContent(key: string) { + getNotificationContent(key: string) { return this.translateService.instant(this.notificationsPrefix + key + '.content'); }