mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
61142: AbstractItemUpdate component and refactoring of item-metadata and item-relationships
This commit is contained in:
@@ -273,6 +273,31 @@
|
|||||||
"content": "Your changes to this item's metadata were saved."
|
"content": "Your changes to this item's metadata were saved."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"relationships": {
|
||||||
|
"discard-button": "Discard",
|
||||||
|
"reinstate-button": "Undo",
|
||||||
|
"save-button": "Save",
|
||||||
|
"edit": {
|
||||||
|
"buttons": {
|
||||||
|
"remove": "Remove",
|
||||||
|
"undo": "Undo changes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"outdated": {
|
||||||
|
"title": "Changed outdated",
|
||||||
|
"content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts"
|
||||||
|
},
|
||||||
|
"discarded": {
|
||||||
|
"title": "Changed discarded",
|
||||||
|
"content": "Your changes were discarded. To reinstate your changes click the 'Undo' button"
|
||||||
|
},
|
||||||
|
"saved": {
|
||||||
|
"title": "Relationships saved",
|
||||||
|
"content": "Your changes to this item's relationships were saved."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -0,0 +1,174 @@
|
|||||||
|
import { Component, Inject, 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';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-abstract-item-update',
|
||||||
|
template: ``,
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Abstract component for managing object updates of an item
|
||||||
|
*/
|
||||||
|
export abstract class AbstractItemUpdateComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The item to display the edit page for
|
||||||
|
*/
|
||||||
|
protected item: Item;
|
||||||
|
/**
|
||||||
|
* The current values and updates for all this item's metadata fields
|
||||||
|
*/
|
||||||
|
protected updates$: Observable<FieldUpdates>;
|
||||||
|
/**
|
||||||
|
* The current url of this page
|
||||||
|
*/
|
||||||
|
protected url: string;
|
||||||
|
/**
|
||||||
|
* Prefix for this component's notification translate keys
|
||||||
|
*/
|
||||||
|
protected notificationsPrefix;
|
||||||
|
/**
|
||||||
|
* The time span for being able to undo discarding changes
|
||||||
|
*/
|
||||||
|
protected discardTimeOut: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected itemService: ItemDataService,
|
||||||
|
protected objectUpdatesService: ObjectUpdatesService,
|
||||||
|
protected router: Router,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
protected route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not there are currently updates for this item
|
||||||
|
*/
|
||||||
|
hasChanges(): Observable<boolean> {
|
||||||
|
return this.objectUpdatesService.hasUpdates(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not the item is currently reinstatable
|
||||||
|
*/
|
||||||
|
isReinstatable(): Observable<boolean> {
|
||||||
|
return this.objectUpdatesService.isReinstatable(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get translated notification title
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
protected getNotificationTitle(key: string) {
|
||||||
|
return this.translateService.instant(this.notificationsPrefix + key + '.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get translated notification content
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
protected getNotificationContent(key: string) {
|
||||||
|
return this.translateService.instant(this.notificationsPrefix + key + '.content');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -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,54 @@ export class ItemMetadataComponent implements OnInit {
|
|||||||
metadataFields$: Observable<string[]>;
|
metadataFields$: Observable<string[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private itemService: ItemDataService,
|
protected itemService: ItemDataService,
|
||||||
private objectUpdatesService: ObjectUpdatesService,
|
protected objectUpdatesService: ObjectUpdatesService,
|
||||||
private router: Router,
|
protected router: Router,
|
||||||
private notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
private translateService: TranslateService,
|
protected translateService: TranslateService,
|
||||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
private route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
private metadataFieldService: RegistryService,
|
protected 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;
|
|
||||||
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);
|
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 +109,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
|
||||||
*/
|
*/
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngFor="let label of relationLabels$ | async" class="mb-2">
|
<div *ngFor="let label of relationLabels$ | async" class="mb-2">
|
||||||
<h5>{{label}}</h5>
|
<h5>{{label}}</h5>
|
||||||
<div *ngFor="let updateValue of ((getUpdatesByLabel(label) | async)| dsObjectValues)"
|
<div *ngFor="let updateValue of ((getUpdatesByLabel(label) | async)| dsObjectValues); trackBy: trackUpdate"
|
||||||
ds-edit-in-place-relationship
|
ds-edit-in-place-relationship
|
||||||
[fieldUpdate]="updateValue || {}"
|
[fieldUpdate]="updateValue || {}"
|
||||||
[url]="url"
|
[url]="url"
|
||||||
|
@@ -1,51 +1,32 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
import { FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { distinctUntilChanged, first, flatMap, map, switchMap } from 'rxjs/operators';
|
||||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
|
||||||
import { distinctUntilChanged, filter, first, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators';
|
|
||||||
import { zip as observableZip } from 'rxjs';
|
import { zip as observableZip } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
import { hasValue, hasValueOperator } from '../../../shared/empty.util';
|
import { hasValue, hasValueOperator } from '../../../shared/empty.util';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
|
||||||
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import {
|
import {
|
||||||
compareArraysUsingIds,
|
compareArraysUsingIds,
|
||||||
filterRelationsByTypeLabel,
|
filterRelationsByTypeLabel,
|
||||||
relationsToItems
|
relationsToItems
|
||||||
} from '../../simple/item-types/shared/item.component';
|
} from '../../simple/item-types/shared/item.component';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
|
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-relationships',
|
selector: 'ds-item-relationships',
|
||||||
styleUrls: ['./item-relationships.component.scss'],
|
styleUrls: ['./item-relationships.component.scss'],
|
||||||
templateUrl: './item-relationships.component.html',
|
templateUrl: './item-relationships.component.html',
|
||||||
})
|
})
|
||||||
export class ItemRelationshipsComponent implements OnInit {
|
/**
|
||||||
|
* Component for displaying an item's relationships edit page
|
||||||
/**
|
*/
|
||||||
* The item to display the edit page for
|
export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||||
*/
|
|
||||||
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;
|
|
||||||
/**
|
|
||||||
* Prefix for this component's notification translate keys
|
|
||||||
*/
|
|
||||||
private notificationsPrefix = 'item.edit.metadata.notifications.';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The labels of all different relations within this item
|
* The labels of all different relations within this item
|
||||||
@@ -56,47 +37,20 @@ export class ItemRelationshipsComponent implements OnInit {
|
|||||||
* Resolved relationships and types together in one observable
|
* Resolved relationships and types together in one observable
|
||||||
*/
|
*/
|
||||||
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>;
|
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>;
|
||||||
/**
|
|
||||||
* The time span for being able to undo discarding changes
|
|
||||||
*/
|
|
||||||
private discardTimeOut: number;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
|
||||||
private router: Router,
|
|
||||||
private translateService: TranslateService,
|
|
||||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
|
||||||
private objectUpdatesService: ObjectUpdatesService,
|
|
||||||
private notificationsService: NotificationsService,
|
|
||||||
private itemDataService: ItemDataService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.parent.data.pipe(map((data) => data.item))
|
super.ngOnInit();
|
||||||
.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.getRelationships().pipe(
|
this.updates$ = this.getRelationships().pipe(
|
||||||
relationsToItems(this.item.id, this.itemDataService),
|
relationsToItems(this.item.id, this.itemService),
|
||||||
switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdates(this.url, items))
|
switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdates(this.url, items))
|
||||||
);
|
);
|
||||||
this.initRelationshipObservables();
|
this.initRelationshipObservables();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the item's relationship observables for easier access across the component
|
||||||
|
*/
|
||||||
initRelationshipObservables() {
|
initRelationshipObservables() {
|
||||||
const relationships$ = this.getRelationships();
|
const relationships$ = this.getRelationships();
|
||||||
|
|
||||||
@@ -119,72 +73,36 @@ export class ItemRelationshipsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent unnecessary rerendering so fields don't lose focus
|
* Initialize the prefix for notification messages
|
||||||
*/
|
*/
|
||||||
trackUpdate(index, update: FieldUpdate) {
|
public initializeNotificationsPrefix(): void {
|
||||||
return update && update.field ? update.field.uuid : undefined;
|
this.notificationsPrefix = 'item.edit.relationships.notifications.';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public submit(): void {
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
discard(): void {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
submit(): void {
|
|
||||||
const updatedItems$ = this.getRelationships().pipe(
|
const updatedItems$ = this.getRelationships().pipe(
|
||||||
first(),
|
first(),
|
||||||
relationsToItems(this.item.id, this.itemDataService),
|
relationsToItems(this.item.id, this.itemService),
|
||||||
switchMap((items: Item[]) => this.objectUpdatesService.getUpdatedFields(this.url, items) as Observable<Item[]>)
|
switchMap((items: Item[]) => this.objectUpdatesService.getUpdatedFields(this.url, items) as Observable<Item[]>)
|
||||||
);
|
);
|
||||||
// TODO: Delete relationships
|
// TODO: Delete relationships
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeOriginalFields() {
|
/**
|
||||||
|
* Sends all initial values of this item to the object updates service
|
||||||
|
*/
|
||||||
|
public initializeOriginalFields() {
|
||||||
this.getRelationships().pipe(
|
this.getRelationships().pipe(
|
||||||
first(),
|
first(),
|
||||||
relationsToItems(this.item.id, this.itemDataService)
|
relationsToItems(this.item.id, this.itemService)
|
||||||
).subscribe((items: Item[]) => {
|
).subscribe((items: Item[]) => {
|
||||||
this.objectUpdatesService.initialize(this.url, items, this.item.lastModified);
|
this.objectUpdatesService.initialize(this.url, items, this.item.lastModified);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current item is still in sync with the version in the store
|
* Fetch all the relationships of the item
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getRelationships(): Observable<Relationship[]> {
|
public getRelationships(): Observable<Relationship[]> {
|
||||||
return this.item.relationships.pipe(
|
return this.item.relationships.pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
@@ -195,34 +113,25 @@ export class ItemRelationshipsComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the item's relationships of a specific type into related items
|
||||||
|
* @param label The relationship type's label
|
||||||
|
*/
|
||||||
public getRelatedItemsByLabel(label: string): Observable<Item[]> {
|
public getRelatedItemsByLabel(label: string): Observable<Item[]> {
|
||||||
return this.resolvedRelsAndTypes$.pipe(
|
return this.resolvedRelsAndTypes$.pipe(
|
||||||
filterRelationsByTypeLabel(label),
|
filterRelationsByTypeLabel(label),
|
||||||
relationsToItems(this.item.id, this.itemDataService)
|
relationsToItems(this.item.id, this.itemService)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get FieldUpdates for the relationships of a specific type
|
||||||
|
* @param label The relationship type's label
|
||||||
|
*/
|
||||||
public getUpdatesByLabel(label: string): Observable<FieldUpdates> {
|
public getUpdatesByLabel(label: string): Observable<FieldUpdates> {
|
||||||
return this.getRelatedItemsByLabel(label).pipe(
|
return this.getRelatedItemsByLabel(label).pipe(
|
||||||
switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items))
|
switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user