mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
63669: Content-Source integrated with FieldUpdate
This commit is contained in:
@@ -155,5 +155,10 @@ module.exports = {
|
|||||||
edit: {
|
edit: {
|
||||||
undoTimeout: 10000 // 10 seconds
|
undoTimeout: 10000 // 10 seconds
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
collection: {
|
||||||
|
edit: {
|
||||||
|
undoTimeout: 10000 // 10 seconds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -55,6 +55,20 @@
|
|||||||
"required": "You must provide a set id of the target collection."
|
"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": {
|
"curate": {
|
||||||
|
@@ -1,4 +1,22 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
<div class="d-inline-block float-right">
|
||||||
|
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
|
[disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="discard()"><i
|
||||||
|
class="fas fa-times"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||||
|
(click)="reinstate()"><i
|
||||||
|
class="fas fa-undo-alt"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="onSubmit()"><i
|
||||||
|
class="fas fa-save"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
|
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
|
||||||
<div class="form-check mb-4">
|
<div class="form-check mb-4">
|
||||||
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="contentSource.enabled" (change)="changeExternalSource()">
|
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="contentSource.enabled" (change)="changeExternalSource()">
|
||||||
@@ -11,6 +29,27 @@
|
|||||||
[formGroup]="formGroup"
|
[formGroup]="formGroup"
|
||||||
[formModel]="formModel"
|
[formModel]="formModel"
|
||||||
[formLayout]="formLayout"
|
[formLayout]="formLayout"
|
||||||
|
[displaySubmit]="false"
|
||||||
(dfChange)="onChange($event)"
|
(dfChange)="onChange($event)"
|
||||||
(submitForm)="onSubmit()"
|
(submitForm)="onSubmit()"
|
||||||
(cancel)="onCancel()"></ds-form>
|
(cancel)="onCancel()"></ds-form>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-inline-block float-right">
|
||||||
|
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
|
[disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="discard()"><i
|
||||||
|
class="fas fa-times"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||||
|
(click)="reinstate()"><i
|
||||||
|
class="fas fa-undo-alt"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="onSubmit()"><i
|
||||||
|
class="fas fa-save"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@@ -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 { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
|
||||||
import {
|
import {
|
||||||
DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService,
|
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 { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { FormGroup } from '@angular/forms';
|
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 { ContentSource } from '../../../core/shared/content-source.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { first, map } from 'rxjs/operators';
|
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
|
* Component for managing the content source of the collection
|
||||||
@@ -26,17 +30,22 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
selector: 'ds-collection-source',
|
selector: 'ds-collection-source',
|
||||||
templateUrl: './collection-source.component.html',
|
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
|
* The current collection's remote data
|
||||||
*/
|
*/
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current collection's content source
|
* The collection's content source
|
||||||
*/
|
*/
|
||||||
contentSource: ContentSource;
|
contentSource: ContentSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current update to the content source
|
||||||
|
*/
|
||||||
|
update$: Observable<FieldUpdate>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string} Key prefix used to generate form labels
|
* @type {string} Key prefix used to generate form labels
|
||||||
*/
|
*/
|
||||||
@@ -80,7 +89,6 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
|
|||||||
new DynamicSelectModel({
|
new DynamicSelectModel({
|
||||||
id: 'format',
|
id: 'format',
|
||||||
name: 'format',
|
name: 'format',
|
||||||
value: 'dc',
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: 'dc',
|
value: 'dc',
|
||||||
@@ -105,7 +113,6 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
|
|||||||
new DynamicRadioGroupModel<number>({
|
new DynamicRadioGroupModel<number>({
|
||||||
id: 'harvest',
|
id: 'harvest',
|
||||||
name: 'harvest',
|
name: 'harvest',
|
||||||
value: 3,
|
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: 1,
|
value: 1,
|
||||||
@@ -171,16 +178,29 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
|
|||||||
*/
|
*/
|
||||||
formGroup: FormGroup;
|
formGroup: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to update the current form
|
||||||
|
*/
|
||||||
|
updateSub: Subscription;
|
||||||
|
|
||||||
public constructor(public objectUpdatesService: ObjectUpdatesService,
|
public constructor(public objectUpdatesService: ObjectUpdatesService,
|
||||||
public notificationsService: NotificationsService,
|
public notificationsService: NotificationsService,
|
||||||
protected location: Location,
|
protected location: Location,
|
||||||
protected formService: DynamicFormService,
|
protected formService: DynamicFormService,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
protected route: ActivatedRoute) {
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,) {
|
||||||
super(objectUpdatesService, notificationsService, translate);
|
super(objectUpdatesService, notificationsService, translate);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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.formGroup = this.formService.createFormGroup(this.formModel);
|
||||||
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
|
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
|
||||||
|
|
||||||
@@ -192,6 +212,33 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
|
|||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.updateFieldTranslations();
|
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) {
|
onChange(event) {
|
||||||
// TODO: Update ContentSource object and add to field update
|
this.updateContentSourceField(event.model);
|
||||||
console.log(event);
|
this.saveFieldUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
// TODO: Fetch field update and send to REST API
|
// TODO: Fetch field update and send to REST API
|
||||||
console.log('submit');
|
this.initializeOriginalContentSource();
|
||||||
|
this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved'));
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel() {
|
onCancel() {
|
||||||
@@ -241,10 +289,46 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
|
|||||||
|
|
||||||
changeExternalSource() {
|
changeExternalSource() {
|
||||||
this.contentSource.enabled = !this.contentSource.enabled;
|
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) {
|
if (this.contentSource.enabled) {
|
||||||
this.formGroup.enable();
|
this.formGroup.enable();
|
||||||
} else {
|
} else {
|
||||||
this.formGroup.disable();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,35 @@
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export class ContentSource {
|
export class ContentSource {
|
||||||
|
/**
|
||||||
|
* Unique identifier
|
||||||
|
*/
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this collection harvest its content from an external source ?
|
||||||
|
*/
|
||||||
enabled = false;
|
enabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAI Provider
|
||||||
|
*/
|
||||||
provider: string;
|
provider: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAI Specific set ID
|
||||||
|
*/
|
||||||
set: string;
|
set: string;
|
||||||
|
|
||||||
format: string;
|
/**
|
||||||
|
* Metadata Format
|
||||||
|
*/
|
||||||
|
format = 'dc';
|
||||||
|
|
||||||
harvestType: number;
|
/**
|
||||||
|
* Type of content being harvested
|
||||||
|
*/
|
||||||
|
harvest = 3;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.uuid = uuid();
|
this.uuid = uuid();
|
||||||
|
@@ -63,7 +63,7 @@ export class AbstractTrackableComponent {
|
|||||||
* Get translated notification title
|
* Get translated notification title
|
||||||
* @param key
|
* @param key
|
||||||
*/
|
*/
|
||||||
private getNotificationTitle(key: string) {
|
protected 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) {
|
protected getNotificationContent(key: string) {
|
||||||
return this.translateService.instant(this.notificationsPrefix + key + '.content');
|
return this.translateService.instant(this.notificationsPrefix + key + '.content');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
7
src/config/collection-page-config.interface.ts
Normal file
7
src/config/collection-page-config.interface.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Config } from './config.interface';
|
||||||
|
|
||||||
|
export interface CollectionPageConfig extends Config {
|
||||||
|
edit: {
|
||||||
|
undoTimeout: number;
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,7 @@ import { FormConfig } from './form-config.interfaces';
|
|||||||
import {LangConfig} from './lang-config.interface';
|
import {LangConfig} from './lang-config.interface';
|
||||||
import { BrowseByConfig } from './browse-by-config.interface';
|
import { BrowseByConfig } from './browse-by-config.interface';
|
||||||
import { ItemPageConfig } from './item-page-config.interface';
|
import { ItemPageConfig } from './item-page-config.interface';
|
||||||
|
import { CollectionPageConfig } from './collection-page-config.interface';
|
||||||
|
|
||||||
export interface GlobalConfig extends Config {
|
export interface GlobalConfig extends Config {
|
||||||
ui: ServerConfig;
|
ui: ServerConfig;
|
||||||
@@ -25,4 +26,5 @@ export interface GlobalConfig extends Config {
|
|||||||
languages: LangConfig[];
|
languages: LangConfig[];
|
||||||
browseBy: BrowseByConfig;
|
browseBy: BrowseByConfig;
|
||||||
item: ItemPageConfig;
|
item: ItemPageConfig;
|
||||||
|
collection: CollectionPageConfig;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user