diff --git a/config/environment.default.js b/config/environment.default.js index 804d80b0f2..ce79909515 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -155,5 +155,10 @@ module.exports = { edit: { undoTimeout: 10000 // 10 seconds } + }, + collection: { + edit: { + undoTimeout: 10000 // 10 seconds + } } }; diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 2aeac293e4..ef688cf661 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -55,6 +55,20 @@ "required": "You must provide a set id of the target collection." } } + }, + "notifications": { + "discarded": { + "title": "Changed discarded", + "content": "Your changes were discarded. To reinstate your changes click the 'Undo' button" + }, + "invalid": { + "title": "Metadata invalid", + "content": "Your changes were not saved. Please make sure all fields are valid before you save." + }, + "saved": { + "title": "Content Source saved", + "content": "Your changes to this collection's content source were saved." + } } }, "curate": { diff --git a/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.html b/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.html index 51a652056b..2017f172c5 100644 --- a/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.html +++ b/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.html @@ -1,4 +1,22 @@
+
+ + + +

{{ 'collection.edit.tabs.source.head' | translate }}

@@ -11,6 +29,27 @@ [formGroup]="formGroup" [formModel]="formModel" [formLayout]="formLayout" + [displaySubmit]="false" (dfChange)="onChange($event)" (submitForm)="onSubmit()" (cancel)="onCancel()"> +
+
+ + + +
+
diff --git a/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.ts b/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.ts index cdff87c6ac..a9696a23f0 100644 --- a/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.ts +++ b/src/app/+collection-page/edit-collection-page/collection-source/collection-source.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component'; import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, @@ -11,13 +11,17 @@ import { TranslateService } from '@ngx-translate/core'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { FormGroup } from '@angular/forms'; -import { isNotEmpty } from '../../../shared/empty.util'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { ContentSource } from '../../../core/shared/content-source.model'; import { Observable } from 'rxjs/internal/Observable'; import { RemoteData } from '../../../core/data/remote-data'; import { Collection } from '../../../core/shared/collection.model'; import { first, map } from 'rxjs/operators'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { cloneDeep } from 'lodash'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config'; /** * Component for managing the content source of the collection @@ -26,17 +30,22 @@ import { ActivatedRoute } from '@angular/router'; selector: 'ds-collection-source', templateUrl: './collection-source.component.html', }) -export class CollectionSourceComponent extends AbstractTrackableComponent implements OnInit { +export class CollectionSourceComponent extends AbstractTrackableComponent implements OnInit, OnDestroy { /** * The current collection's remote data */ collectionRD$: Observable>; /** - * The current collection's content source + * The collection's content source */ contentSource: ContentSource; + /** + * The current update to the content source + */ + update$: Observable; + /** * @type {string} Key prefix used to generate form labels */ @@ -80,7 +89,6 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem new DynamicSelectModel({ id: 'format', name: 'format', - value: 'dc', options: [ { value: 'dc', @@ -105,7 +113,6 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem new DynamicRadioGroupModel({ id: 'harvest', name: 'harvest', - value: 3, options: [ { value: 1, @@ -171,16 +178,29 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem */ formGroup: FormGroup; + /** + * Subscription to update the current form + */ + updateSub: Subscription; + public constructor(public objectUpdatesService: ObjectUpdatesService, public notificationsService: NotificationsService, protected location: Location, protected formService: DynamicFormService, protected translate: TranslateService, - protected route: ActivatedRoute) { + protected route: ActivatedRoute, + protected router: Router, + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,) { super(objectUpdatesService, notificationsService, translate); } ngOnInit(): void { + this.notificationsPrefix = 'collection.edit.tabs.source.notifications.'; + this.discardTimeOut = this.EnvConfig.collection.edit.undoTimeout; + this.url = this.router.url; + if (this.url.indexOf('?') > 0) { + this.url = this.url.substr(0, this.url.indexOf('?')); + } this.formGroup = this.formService.createFormGroup(this.formModel); this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso)); @@ -192,6 +212,33 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem .subscribe(() => { this.updateFieldTranslations(); }); + + this.initializeOriginalContentSource(); + } + + initializeOriginalContentSource() { + const initialContentSource = cloneDeep(this.contentSource); + this.objectUpdatesService.initialize(this.url, [initialContentSource], new Date()); + this.update$ = this.objectUpdatesService.getFieldUpdates(this.url, [initialContentSource]).pipe( + map((updates: FieldUpdates) => updates[initialContentSource.uuid]) + ); + this.updateSub = this.update$.subscribe((update: FieldUpdate) => { + const field = update.field as ContentSource; + this.formGroup.patchValue({ + providerContainer: { + provider: field.provider + }, + setContainer: { + set: field.set, + format: field.format + }, + harvestContainer: { + harvest: field.harvest + } + }); + this.contentSource = cloneDeep(field); + this.switchEnableForm(); + }); } /** @@ -226,13 +273,14 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem } onChange(event) { - // TODO: Update ContentSource object and add to field update - console.log(event); + this.updateContentSourceField(event.model); + this.saveFieldUpdate(); } onSubmit() { // TODO: Fetch field update and send to REST API - console.log('submit'); + this.initializeOriginalContentSource(); + this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); } onCancel() { @@ -241,10 +289,46 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem changeExternalSource() { this.contentSource.enabled = !this.contentSource.enabled; + this.updateContentSource(); + } + + /** + * Enable or disable the form depending on the Content Source's enabled property + */ + switchEnableForm() { if (this.contentSource.enabled) { this.formGroup.enable(); } else { this.formGroup.disable(); } } + + updateContentSource() { + this.formModel.forEach( + (fieldModel: DynamicFormGroupModel | DynamicInputModel) => { + if (fieldModel instanceof DynamicFormGroupModel) { + fieldModel.group.forEach((childModel: DynamicInputModel) => { + this.updateContentSourceField(childModel); + }); + } else { + this.updateContentSourceField(fieldModel); + } + } + ); + this.saveFieldUpdate(); + } + + updateContentSourceField(fieldModel: DynamicInputModel) { + if (hasValue(fieldModel)) { + this.contentSource[fieldModel.id] = fieldModel.value; + } + } + + saveFieldUpdate() { + this.objectUpdatesService.saveAddFieldUpdate(this.url, cloneDeep(this.contentSource)); + } + + ngOnDestroy(): void { + this.updateSub.unsubscribe(); + } } diff --git a/src/app/core/shared/content-source.model.ts b/src/app/core/shared/content-source.model.ts index 10f0986b1b..4c523b80e1 100644 --- a/src/app/core/shared/content-source.model.ts +++ b/src/app/core/shared/content-source.model.ts @@ -1,17 +1,35 @@ import { v4 as uuid } from 'uuid'; export class ContentSource { + /** + * Unique identifier + */ uuid: string; + /** + * Does this collection harvest its content from an external source ? + */ enabled = false; + /** + * OAI Provider + */ provider: string; + /** + * OAI Specific set ID + */ set: string; - format: string; + /** + * Metadata Format + */ + format = 'dc'; - harvestType: number; + /** + * Type of content being harvested + */ + harvest = 3; constructor() { this.uuid = uuid(); diff --git a/src/app/shared/trackable/abstract-trackable.component.ts b/src/app/shared/trackable/abstract-trackable.component.ts index cd1b425f10..bb1f4b31b4 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) { + protected 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) { + protected getNotificationContent(key: string) { return this.translateService.instant(this.notificationsPrefix + key + '.content'); } diff --git a/src/config/collection-page-config.interface.ts b/src/config/collection-page-config.interface.ts new file mode 100644 index 0000000000..b0103fd176 --- /dev/null +++ b/src/config/collection-page-config.interface.ts @@ -0,0 +1,7 @@ +import { Config } from './config.interface'; + +export interface CollectionPageConfig extends Config { + edit: { + undoTimeout: number; + } +} diff --git a/src/config/global-config.interface.ts b/src/config/global-config.interface.ts index d83ec6e4d8..e2409ec18b 100644 --- a/src/config/global-config.interface.ts +++ b/src/config/global-config.interface.ts @@ -8,6 +8,7 @@ import { FormConfig } from './form-config.interfaces'; import {LangConfig} from './lang-config.interface'; import { BrowseByConfig } from './browse-by-config.interface'; import { ItemPageConfig } from './item-page-config.interface'; +import { CollectionPageConfig } from './collection-page-config.interface'; export interface GlobalConfig extends Config { ui: ServerConfig; @@ -25,4 +26,5 @@ export interface GlobalConfig extends Config { languages: LangConfig[]; browseBy: BrowseByConfig; item: ItemPageConfig; + collection: CollectionPageConfig; }