63945: Abstract item-update component

This commit is contained in:
Kristof De Langhe
2019-07-25 13:39:28 +02:00
parent cdb2c30bd4
commit 4f3ec612a1
3 changed files with 149 additions and 135 deletions

View File

@@ -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;
}

View File

@@ -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 { Item } from '../../../core/shared/item.model';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.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 { cloneDeep } from 'lodash';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { import {
FieldUpdate,
FieldUpdates,
Identifiable Identifiable
} from '../../../core/data/object-updates/object-updates.reducer'; } from '../../../core/data/object-updates/object-updates.reducer';
import { first, map, switchMap, take, tap } from 'rxjs/operators'; 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 { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadatumViewModel } from '../../../core/shared/metadata.models'; import { MetadatumViewModel } from '../../../core/shared/metadata.models';
import { Metadata } from '../../../core/shared/metadata.utils'; import { Metadata } from '../../../core/shared/metadata.utils';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
@Component({ @Component({
selector: 'ds-item-metadata', selector: 'ds-item-metadata',
@@ -29,28 +28,7 @@ import { Metadata } from '../../../core/shared/metadata.utils';
/** /**
* Component for displaying an item's metadata edit page * Component for displaying an item's metadata edit page
*/ */
export class ItemMetadataComponent implements OnInit { export class ItemMetadataComponent extends AbstractItemUpdateComponent {
/**
* 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.';
/** /**
* Observable with a list of strings with all existing metadata field keys * Observable with a list of strings with all existing metadata field keys
@@ -58,90 +36,60 @@ export class ItemMetadataComponent implements OnInit {
metadataFields$: Observable<string[]>; metadataFields$: Observable<string[]>;
constructor( constructor(
private itemService: ItemDataService, public itemService: ItemDataService,
private objectUpdatesService: ObjectUpdatesService, public objectUpdatesService: ObjectUpdatesService,
private router: Router, public router: Router,
private notificationsService: NotificationsService, public notificationsService: NotificationsService,
private translateService: TranslateService, public translateService: TranslateService,
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, @Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
private route: ActivatedRoute, public route: ActivatedRoute,
private metadataFieldService: RegistryService, public metadataFieldService: RegistryService,
) { ) {
super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route);
} }
/** /**
* Set up and initialize all fields * Set up and initialize all fields
*/ */
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit();
this.metadataFields$ = this.findMetadataFields(); 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; * Initialize the values and updates of the current item's metadata fields
if (this.url.indexOf('?') > 0) { */
this.url = this.url.substr(0, this.url.indexOf('?')); public initializeUpdates(): void {
}
this.hasChanges().pipe(first()).subscribe((hasChanges) => {
if (!hasChanges) {
this.initializeOriginalFields();
} else {
this.checkLastModified();
}
});
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList);
} }
/**
* Initialize the prefix for notification messages
*/
public initializeNotificationsPrefix(): void {
this.notificationsPrefix = 'item.edit.metadata.notifications.';
}
/** /**
* Sends a new add update for a field to the object updates service * 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 * @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum
*/ */
add(metadata: MetadatumViewModel = new MetadatumViewModel()) { add(metadata: MetadatumViewModel = new MetadatumViewModel()) {
this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata); this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata);
}
/**
* 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
*/
discard() {
const undoNotification = this.notificationsService.info(this.getNotificationTitle('discarded'), this.getNotificationContent('discarded'), { timeOut: this.discardTimeOut });
this.objectUpdatesService.discardFieldUpdates(this.url, undoNotification);
}
/**
* Request the object updates service to undo discarding all changes to this item
*/
reinstate() {
this.objectUpdatesService.reinstateFieldUpdates(this.url);
} }
/** /**
* Sends all initial values of this item to the object updates service * 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); 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 * 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 * Makes sure the new version of the item is rendered on the page
*/ */
submit() { public submit() {
this.isValid().pipe(first()).subscribe((isValid) => { this.isValid().pipe(first()).subscribe((isValid) => {
if (isValid) { if (isValid) {
const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadataAsList) as Observable<MetadatumViewModel[]>; 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 * Method to request all metadata fields and convert them to a list of strings
*/ */

View File

@@ -63,7 +63,7 @@ export class AbstractTrackableComponent {
* Get translated notification title * Get translated notification title
* @param key * @param key
*/ */
private getNotificationTitle(key: string) { getNotificationTitle(key: string) {
return this.translateService.instant(this.notificationsPrefix + key + '.title'); return this.translateService.instant(this.notificationsPrefix + key + '.title');
} }
@@ -71,7 +71,7 @@ export class AbstractTrackableComponent {
* Get translated notification content * Get translated notification content
* @param key * @param key
*/ */
private getNotificationContent(key: string) { getNotificationContent(key: string) {
return this.translateService.instant(this.notificationsPrefix + key + '.content'); return this.translateService.instant(this.notificationsPrefix + key + '.content');
} }