mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 14:33:03 +00:00
63945: Abstract item-update component
This commit is contained in:
@@ -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;
|
||||||
|
}
|
@@ -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
|
||||||
*/
|
*/
|
||||||
|
@@ -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');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user