Added more comments

This commit is contained in:
Giuseppe Digilio
2019-03-20 19:57:07 +01:00
parent 825464bb9c
commit 6335d61dda
26 changed files with 1505 additions and 123 deletions

View File

@@ -743,7 +743,7 @@
"upload-successful": "Upload successful", "upload-successful": "Upload successful",
"upload-failed": "Upload failed", "upload-failed": "Upload failed",
"header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):", "header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):",
"header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicity decided for the single file, with the following group(s):", "header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"form": { "form": {
"access-condition-label": "Access condition type", "access-condition-label": "Access condition type",
"from-label": "Access grant from", "from-label": "Access grant from",

View File

@@ -14,17 +14,44 @@ import { SubmissionObject } from '../../core/submission/models/submission-object
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
/**
* This component allows to edit an existing workspaceitem/workflowitem.
*/
@Component({ @Component({
selector: 'ds-submission-edit', selector: 'ds-submission-edit',
styleUrls: ['./submission-edit.component.scss'], styleUrls: ['./submission-edit.component.scss'],
templateUrl: './submission-edit.component.html' templateUrl: './submission-edit.component.html'
}) })
export class SubmissionEditComponent implements OnDestroy, OnInit { export class SubmissionEditComponent implements OnDestroy, OnInit {
/**
* The collection id this submission belonging to
* @type {string}
*/
public collectionId: string; public collectionId: string;
/**
* The list of submission's sections
* @type {WorkspaceitemSectionsObject}
*/
public sections: WorkspaceitemSectionsObject; public sections: WorkspaceitemSectionsObject;
/**
* The submission self url
* @type {string}
*/
public selfUrl: string; public selfUrl: string;
/**
* The configuration object that define this submission
* @type {SubmissionDefinitionsModel}
*/
public submissionDefinition: SubmissionDefinitionsModel; public submissionDefinition: SubmissionDefinitionsModel;
/**
* The submission id
* @type {string}
*/
public submissionId: string; public submissionId: string;
/** /**
@@ -33,6 +60,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
*/ */
private subs: Subscription[] = []; private subs: Subscription[] = [];
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} changeDetectorRef
* @param {NotificationsService} notificationsService
* @param {ActivatedRoute} route
* @param {Router} router
* @param {SubmissionService} submissionService
* @param {TranslateService} translate
*/
constructor(private changeDetectorRef: ChangeDetectorRef, constructor(private changeDetectorRef: ChangeDetectorRef,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -41,6 +78,9 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
private translate: TranslateService) { private translate: TranslateService) {
} }
/**
* Retrieve workspaceitem/workflowitem from server and initialize all instance variables
*/
ngOnInit() { ngOnInit() {
this.subs.push(this.route.paramMap.pipe( this.subs.push(this.route.paramMap.pipe(
switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))),
@@ -70,7 +110,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
} }
/** /**
* Method provided by Angular. Invoked when the instance is destroyed. * Unsubscribe from all subscriptions
*/ */
ngOnDestroy() { ngOnDestroy() {
this.subs this.subs

View File

@@ -36,24 +36,48 @@ import { SubmissionService } from '../../submission.service';
import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
/**
* An interface to represent a collection entry
*/
interface CollectionListEntryItem { interface CollectionListEntryItem {
id: string; id: string;
name: string; name: string;
} }
/**
* An interface to represent an entry in the collection list
*/
interface CollectionListEntry { interface CollectionListEntry {
communities: CollectionListEntryItem[], communities: CollectionListEntryItem[],
collection: CollectionListEntryItem collection: CollectionListEntryItem
} }
/**
* This component allows to show the current collection the submission belonging to and to change it.
*/
@Component({ @Component({
selector: 'ds-submission-form-collection', selector: 'ds-submission-form-collection',
styleUrls: ['./submission-form-collection.component.scss'], styleUrls: ['./submission-form-collection.component.scss'],
templateUrl: './submission-form-collection.component.html' templateUrl: './submission-form-collection.component.html'
}) })
export class SubmissionFormCollectionComponent implements OnChanges, OnInit { export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
/**
* The current collection id this submission belonging to
* @type {string}
*/
@Input() currentCollectionId: string; @Input() currentCollectionId: string;
/**
* The current configuration object that define this submission
* @type {SubmissionDefinitionsModel}
*/
@Input() currentDefinition: string; @Input() currentDefinition: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId; @Input() submissionId;
/** /**
@@ -62,18 +86,69 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
*/ */
@Output() collectionChange: EventEmitter<SubmissionObject> = new EventEmitter<SubmissionObject>(); @Output() collectionChange: EventEmitter<SubmissionObject> = new EventEmitter<SubmissionObject>();
/**
* A boolean representing if this dropdown button is disabled
* @type {BehaviorSubject<boolean>}
*/
public disabled$ = new BehaviorSubject<boolean>(true); public disabled$ = new BehaviorSubject<boolean>(true);
public model: any;
/**
* The search form control
* @type {FormControl}
*/
public searchField: FormControl = new FormControl(); public searchField: FormControl = new FormControl();
/**
* The collection list obtained from a search
* @type {Observable<CollectionListEntry[]>}
*/
public searchListCollection$: Observable<CollectionListEntry[]>; public searchListCollection$: Observable<CollectionListEntry[]>;
/**
* The selected collection id
* @type {string}
*/
public selectedCollectionId: string; public selectedCollectionId: string;
/**
* The selected collection name
* @type {Observable<string>}
*/
public selectedCollectionName$: Observable<string>; public selectedCollectionName$: Observable<string>;
/**
* The JsonPatchOperationPathCombiner object
* @type {JsonPatchOperationPathCombiner}
*/
protected pathCombiner: JsonPatchOperationPathCombiner; protected pathCombiner: JsonPatchOperationPathCombiner;
/**
* A boolean representing if dropdown list is scrollable to the bottom
* @type {boolean}
*/
private scrollableBottom = false; private scrollableBottom = false;
/**
* A boolean representing if dropdown list is scrollable to the top
* @type {boolean}
*/
private scrollableTop = false; private scrollableTop = false;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
private subs: Subscription[] = []; private subs: Subscription[] = [];
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} cdr
* @param {CommunityDataService} communityDataService
* @param {JsonPatchOperationsBuilder} operationsBuilder
* @param {SubmissionJsonPatchOperationsService} operationsService
* @param {SubmissionService} submissionService
*/
constructor(protected cdr: ChangeDetectorRef, constructor(protected cdr: ChangeDetectorRef,
private communityDataService: CommunityDataService, private communityDataService: CommunityDataService,
private operationsBuilder: JsonPatchOperationsBuilder, private operationsBuilder: JsonPatchOperationsBuilder,
@@ -81,6 +156,13 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
private submissionService: SubmissionService) { private submissionService: SubmissionService) {
} }
/**
* Method called on mousewheel event, it prevent the page scroll
* when arriving at the top/bottom of dropdown menu
*
* @param event
* mousewheel event
*/
@HostListener('mousewheel', ['$event']) onMousewheel(event) { @HostListener('mousewheel', ['$event']) onMousewheel(event) {
if (event.wheelDelta > 0 && this.scrollableTop) { if (event.wheelDelta > 0 && this.scrollableTop) {
event.preventDefault(); event.preventDefault();
@@ -90,11 +172,19 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
} }
} }
/**
* Check if dropdown scrollbar is at the top or bottom of the dropdown list
*
* @param event
*/
onScroll(event) { onScroll(event) {
this.scrollableBottom = (event.target.scrollTop + event.target.clientHeight === event.target.scrollHeight); this.scrollableBottom = (event.target.scrollTop + event.target.clientHeight === event.target.scrollHeight);
this.scrollableTop = (event.target.scrollTop === 0); this.scrollableTop = (event.target.scrollTop === 0);
} }
/**
* Initialize collection list
*/
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (hasValue(changes.currentCollectionId) if (hasValue(changes.currentCollectionId)
&& hasValue(changes.currentCollectionId.currentValue)) { && hasValue(changes.currentCollectionId.currentValue)) {
@@ -153,14 +243,26 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
} }
} }
/**
* Initialize all instance variables
*/
ngOnInit() { ngOnInit() {
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 'collection'); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 'collection');
} }
/**
* Unsubscribe from all subscriptions
*/
ngOnDestroy(): void { ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
} }
/**
* Emit a [collectionChange] event when a new collection is selected from list
*
* @param event
* the selected [CollectionListEntryItem]
*/
onSelect(event) { onSelect(event) {
this.searchField.reset(); this.searchField.reset();
this.disabled$.next(true); this.disabled$.next(true);
@@ -181,10 +283,19 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
); );
} }
/**
* Reset search form control on dropdown menu close
*/
onClose() { onClose() {
this.searchField.reset(); this.searchField.reset();
} }
/**
* Reset search form control when dropdown menu is closed
*
* @param isOpen
* Representing if the dropdown menu is open or not.
*/
toggled(isOpen: boolean) { toggled(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
this.searchField.reset(); this.searchField.reset();

View File

@@ -9,6 +9,9 @@ import { SubmissionService } from '../../submission.service';
import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; import { SubmissionScopeType } from '../../../core/submission/submission-scope-type';
import { isNotEmpty } from '../../../shared/empty.util'; import { isNotEmpty } from '../../../shared/empty.util';
/**
* This component represents submission form footer bar.
*/
@Component({ @Component({
selector: 'ds-submission-form-footer', selector: 'ds-submission-form-footer',
styleUrls: ['./submission-form-footer.component.scss'], styleUrls: ['./submission-form-footer.component.scss'],
@@ -16,18 +19,51 @@ import { isNotEmpty } from '../../../shared/empty.util';
}) })
export class SubmissionFormFooterComponent implements OnChanges { export class SubmissionFormFooterComponent implements OnChanges {
@Input() submissionId; /**
* The submission id
* @type {string}
*/
@Input() submissionId: string;
/**
* A boolean representing if a submission deposit operation is pending
* @type {Observable<boolean>}
*/
public processingDepositStatus: Observable<boolean>; public processingDepositStatus: Observable<boolean>;
/**
* A boolean representing if a submission save operation is pending
* @type {Observable<boolean>}
*/
public processingSaveStatus: Observable<boolean>; public processingSaveStatus: Observable<boolean>;
/**
* A boolean representing if showing deposit and discard buttons
* @type {Observable<boolean>}
*/
public showDepositAndDiscard: Observable<boolean>; public showDepositAndDiscard: Observable<boolean>;
/**
* A boolean representing if submission form is valid or not
* @type {Observable<boolean>}
*/
private submissionIsInvalid: Observable<boolean> = observableOf(true); private submissionIsInvalid: Observable<boolean> = observableOf(true);
/**
* Initialize instance variables
*
* @param {NgbModal} modalService
* @param {SubmissionRestService} restService
* @param {SubmissionService} submissionService
*/
constructor(private modalService: NgbModal, constructor(private modalService: NgbModal,
private restService: SubmissionRestService, private restService: SubmissionRestService,
private submissionService: SubmissionService) { private submissionService: SubmissionService) {
} }
/**
* Initialize all instance variables
*/
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (isNotEmpty(this.submissionId)) { if (isNotEmpty(this.submissionId)) {
this.submissionIsInvalid = this.submissionService.getSubmissionStatus(this.submissionId).pipe( this.submissionIsInvalid = this.submissionService.getSubmissionStatus(this.submissionId).pipe(
@@ -40,18 +76,30 @@ export class SubmissionFormFooterComponent implements OnChanges {
} }
} }
/**
* Dispatch a submission save action
*/
save(event) { save(event) {
this.submissionService.dispatchSave(this.submissionId); this.submissionService.dispatchSave(this.submissionId);
} }
/**
* Dispatch a submission save for later action
*/
saveLater(event) { saveLater(event) {
this.submissionService.dispatchSaveForLater(this.submissionId); this.submissionService.dispatchSaveForLater(this.submissionId);
} }
/**
* Dispatch a submission deposit action
*/
public deposit(event) { public deposit(event) {
this.submissionService.dispatchDeposit(this.submissionId); this.submissionService.dispatchDeposit(this.submissionId);
} }
/**
* Dispatch a submission discard action
*/
public confirmDiscard(content) { public confirmDiscard(content) {
this.modalService.open(content).result.then( this.modalService.open(content).result.then(
(result) => { (result) => {

View File

@@ -1,30 +1,62 @@
import { Component, Input, OnInit, } from '@angular/core'; import { Component, Input, OnInit, } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SectionsService } from '../../sections/sections.service'; import { SectionsService } from '../../sections/sections.service';
import { HostWindowService } from '../../../shared/host-window.service'; import { HostWindowService } from '../../../shared/host-window.service';
import { SubmissionService } from '../../submission.service'; import { SubmissionService } from '../../submission.service';
import { SectionDataObject } from '../../sections/models/section-data.model'; import { SectionDataObject } from '../../sections/models/section-data.model';
import { map } from 'rxjs/operators';
/**
* This component allow to add any new section to submission form
*/
@Component({ @Component({
selector: 'ds-submission-form-section-add', selector: 'ds-submission-form-section-add',
styleUrls: [ './submission-form-section-add.component.scss' ], styleUrls: [ './submission-form-section-add.component.scss' ],
templateUrl: './submission-form-section-add.component.html' templateUrl: './submission-form-section-add.component.html'
}) })
export class SubmissionFormSectionAddComponent implements OnInit { export class SubmissionFormSectionAddComponent implements OnInit {
/**
* The collection id this submission belonging to
* @type {string}
*/
@Input() collectionId: string; @Input() collectionId: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string; @Input() submissionId: string;
/**
* The possible section list to add
* @type {Observable<SectionDataObject[]>}
*/
public sectionList$: Observable<SectionDataObject[]>; public sectionList$: Observable<SectionDataObject[]>;
/**
* A boolean representing if there are available sections to add
* @type {Observable<boolean>}
*/
public hasSections$: Observable<boolean>; public hasSections$: Observable<boolean>;
/**
* Initialize instance variables
*
* @param {SectionsService} sectionService
* @param {SubmissionService} submissionService
* @param {HostWindowService} windowService
*/
constructor(private sectionService: SectionsService, constructor(private sectionService: SectionsService,
private submissionService: SubmissionService, private submissionService: SubmissionService,
public windowService: HostWindowService) { public windowService: HostWindowService) {
} }
/**
* Initialize all instance variables
*/
ngOnInit() { ngOnInit() {
this.sectionList$ = this.submissionService.getDisabledSectionsList(this.submissionId); this.sectionList$ = this.submissionService.getDisabledSectionsList(this.submissionId);
this.hasSections$ = this.sectionList$.pipe( this.hasSections$ = this.sectionList$.pipe(
@@ -32,6 +64,9 @@ export class SubmissionFormSectionAddComponent implements OnInit {
) )
} }
/**
* Dispatch an action to add a new section
*/
addSection(sectionId) { addSection(sectionId) {
this.sectionService.addSection(this.submissionId, sectionId); this.sectionService.addSection(this.submissionId, sectionId);
} }

View File

@@ -15,22 +15,68 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { SubmissionObject } from '../../core/submission/models/submission-object.model';
/**
* This component represents the submission form.
*/
@Component({ @Component({
selector: 'ds-submission-submit-form', selector: 'ds-submission-submit-form',
styleUrls: ['./submission-form.component.scss'], styleUrls: ['./submission-form.component.scss'],
templateUrl: './submission-form.component.html', templateUrl: './submission-form.component.html',
}) })
export class SubmissionFormComponent implements OnChanges, OnDestroy { export class SubmissionFormComponent implements OnChanges, OnDestroy {
/**
* The collection id this submission belonging to
* @type {string}
*/
@Input() collectionId: string; @Input() collectionId: string;
/**
* The list of submission's sections
* @type {WorkspaceitemSectionsObject}
*/
@Input() sections: WorkspaceitemSectionsObject; @Input() sections: WorkspaceitemSectionsObject;
/**
* The submission self url
* @type {string}
*/
@Input() selfUrl: string; @Input() selfUrl: string;
/**
* The configuration object that define this submission
* @type {SubmissionDefinitionsModel}
*/
@Input() submissionDefinition: SubmissionDefinitionsModel; @Input() submissionDefinition: SubmissionDefinitionsModel;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string; @Input() submissionId: string;
/**
* The configuration id that define this submission
* @type {string}
*/
public definitionId: string; public definitionId: string;
public test = true;
/**
* A boolean representing if a submission form is pending
* @type {Observable<boolean>}
*/
public loading: Observable<boolean> = observableOf(true); public loading: Observable<boolean> = observableOf(true);
public submissionSections: Observable<any>;
/**
* Observable of the list of submission's sections
* @type {Observable<WorkspaceitemSectionsObject>}
*/
public submissionSections: Observable<WorkspaceitemSectionsObject>;
/**
* The uploader configuration options
* @type {UploaderOptions}
*/
public uploadFilesOptions: UploaderOptions = { public uploadFilesOptions: UploaderOptions = {
url: '', url: '',
authToken: null, authToken: null,
@@ -38,9 +84,26 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
itemAlias: null itemAlias: null
}; };
/**
* A boolean representing if component is active
* @type {boolean}
*/
protected isActive: boolean; protected isActive: boolean;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
/**
* Initialize instance variables
*
* @param {AuthService} authService
* @param {ChangeDetectorRef} changeDetectorRef
* @param {HALEndpointService} halService
* @param {SubmissionService} submissionService
*/
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@@ -49,9 +112,14 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
this.isActive = true; this.isActive = true;
} }
/**
* Initialize all instance variables and retrieve form configuration
*/
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (this.collectionId && this.submissionId) { if (this.collectionId && this.submissionId) {
this.isActive = true; this.isActive = true;
// retrieve submission's section list
this.submissionSections = this.submissionService.getSubmissionObject(this.submissionId).pipe( this.submissionSections = this.submissionService.getSubmissionObject(this.submissionId).pipe(
filter(() => this.isActive), filter(() => this.isActive),
map((submission: SubmissionObjectEntry) => submission.isLoading), map((submission: SubmissionObjectEntry) => submission.isLoading),
@@ -65,12 +133,14 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
} }
})); }));
// check if is submission loading
this.loading = this.submissionService.getSubmissionObject(this.submissionId).pipe( this.loading = this.submissionService.getSubmissionObject(this.submissionId).pipe(
filter(() => this.isActive), filter(() => this.isActive),
map((submission: SubmissionObjectEntry) => submission.isLoading), map((submission: SubmissionObjectEntry) => submission.isLoading),
map((isLoading: boolean) => isLoading), map((isLoading: boolean) => isLoading),
distinctUntilChanged()); distinctUntilChanged());
// init submission state
this.subs.push( this.subs.push(
this.halService.getEndpoint('workspaceitems').pipe( this.halService.getEndpoint('workspaceitems').pipe(
filter((href: string) => isNotEmpty(href)), filter((href: string) => isNotEmpty(href)),
@@ -89,10 +159,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
}) })
); );
// start auto save
this.submissionService.startAutoSave(this.submissionId); this.submissionService.startAutoSave(this.submissionId);
} }
} }
/**
* Unsubscribe from all subscriptions, destroy instance variables
* and reset submission state
*/
ngOnDestroy() { ngOnDestroy() {
this.isActive = false; this.isActive = false;
this.submissionService.stopAutoSave(); this.submissionService.stopAutoSave();
@@ -102,6 +178,13 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
.forEach((subscription) => subscription.unsubscribe()); .forEach((subscription) => subscription.unsubscribe());
} }
/**
* On collection change reset submission state in case of it has a different
* submission definition
*
* @param submissionObject
* new submission object
*/
onCollectionChange(submissionObject: SubmissionObject) { onCollectionChange(submissionObject: SubmissionObject) {
this.collectionId = (submissionObject.collection as Collection).id; this.collectionId = (submissionObject.collection as Collection).id;
if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) { if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) {
@@ -119,10 +202,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
} }
} }
/**
* Check if submission form is loading
*/
isLoading(): Observable<boolean> { isLoading(): Observable<boolean> {
return this.loading; return this.loading;
} }
/**
* Check if submission form is loading
*/
protected getSectionsList(): Observable<any> { protected getSectionsList(): Observable<any> {
return this.submissionService.getSubmissionSections(this.submissionId).pipe( return this.submissionService.getSubmissionSections(this.submissionId).pipe(
filter((sections: SectionDataObject[]) => isNotEmpty(sections)), filter((sections: SectionDataObject[]) => isNotEmpty(sections)),

View File

@@ -14,24 +14,72 @@ import { UploaderOptions } from '../../../shared/uploader/uploader-options.model
import parseSectionErrors from '../../utils/parseSectionErrors'; import parseSectionErrors from '../../utils/parseSectionErrors';
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
/**
* This component represents the drop zone that provides to add files to the submission.
*/
@Component({ @Component({
selector: 'ds-submission-upload-files', selector: 'ds-submission-upload-files',
templateUrl: './submission-upload-files.component.html', templateUrl: './submission-upload-files.component.html',
}) })
export class SubmissionUploadFilesComponent implements OnChanges { export class SubmissionUploadFilesComponent implements OnChanges {
@Input() collectionId; /**
@Input() submissionId; * The collection id this submission belonging to
@Input() sectionId; * @type {string}
*/
@Input() collectionId: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string;
/**
* The upload section id
* @type {string}
*/
@Input() sectionId: string;
/**
* The uploader configuration options
* @type {UploaderOptions}
*/
@Input() uploadFilesOptions: UploaderOptions; @Input() uploadFilesOptions: UploaderOptions;
/**
* A boolean representing if is possible to active drop zone over the document page
* @type {boolean}
*/
public enableDragOverDocument = true; public enableDragOverDocument = true;
/**
* i18n message label
* @type {string}
*/
public dropOverDocumentMsg = 'submission.sections.upload.drop-message'; public dropOverDocumentMsg = 'submission.sections.upload.drop-message';
/**
* i18n message label
* @type {string}
*/
public dropMsg = 'submission.sections.upload.drop-message'; public dropMsg = 'submission.sections.upload.drop-message';
private subs = []; /**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
private subs: Subscription[] = [];
/**
* A boolean representing if upload functionality is enabled
* @type {boolean}
*/
private uploadEnabled: Observable<boolean> = observableOf(false); private uploadEnabled: Observable<boolean> = observableOf(false);
/**
* Save submission before to upload a file
*/
public onBeforeUpload = () => { public onBeforeUpload = () => {
const sub: Subscription = this.operationsService.jsonPatchByResourceType( const sub: Subscription = this.operationsService.jsonPatchByResourceType(
this.submissionService.getSubmissionObjectLinkName(), this.submissionService.getSubmissionObjectLinkName(),
@@ -42,6 +90,15 @@ export class SubmissionUploadFilesComponent implements OnChanges {
return sub; return sub;
}; };
/**
* Initialize instance variables
*
* @param {NotificationsService} notificationsService
* @param {SubmissionJsonPatchOperationsService} operationsService
* @param {SectionsService} sectionService
* @param {SubmissionService} submissionService
* @param {TranslateService} translate
*/
constructor(private notificationsService: NotificationsService, constructor(private notificationsService: NotificationsService,
private operationsService: SubmissionJsonPatchOperationsService, private operationsService: SubmissionJsonPatchOperationsService,
private sectionService: SectionsService, private sectionService: SectionsService,
@@ -49,10 +106,19 @@ export class SubmissionUploadFilesComponent implements OnChanges {
private translate: TranslateService) { private translate: TranslateService) {
} }
/**
* Check if upload functionality is enabled
*/
ngOnChanges() { ngOnChanges() {
this.uploadEnabled = this.sectionService.isSectionAvailable(this.submissionId, this.sectionId); this.uploadEnabled = this.sectionService.isSectionAvailable(this.submissionId, this.sectionId);
} }
/**
* Parse the submission object retrieved from REST after upload
*
* @param workspaceitem
* The submission object retrieved from REST
*/
public onCompleteItem(workspaceitem: Workspaceitem) { public onCompleteItem(workspaceitem: Workspaceitem) {
// Checks if upload section is enabled so do upload // Checks if upload section is enabled so do upload
this.subs.push( this.subs.push(
@@ -61,8 +127,8 @@ export class SubmissionUploadFilesComponent implements OnChanges {
.subscribe((isUploadEnabled) => { .subscribe((isUploadEnabled) => {
if (isUploadEnabled) { if (isUploadEnabled) {
const {sections} = workspaceitem; const { sections } = workspaceitem;
const {errors} = workspaceitem; const { errors } = workspaceitem;
const errorsList = parseSectionErrors(errors); const errorsList = parseSectionErrors(errors);
if (sections && isNotEmpty(sections)) { if (sections && isNotEmpty(sections)) {
@@ -87,12 +153,15 @@ export class SubmissionUploadFilesComponent implements OnChanges {
); );
} }
/**
* Show error notification on upload fails
*/
public onUploadError() { public onUploadError() {
this.notificationsService.error(null, this.translate.get('submission.sections.upload.upload-failed')); this.notificationsService.error(null, this.translate.get('submission.sections.upload.upload-failed'));
} }
/** /**
* Method provided by Angular. Invoked when the instance is destroyed. * Unsubscribe from all subscriptions
*/ */
ngOnDestroy() { ngOnDestroy() {
this.subs this.subs

View File

@@ -277,7 +277,7 @@ export class SubmissionObjectEffects {
} }
/** /**
* Parse the submission object retrieved from REST haven't section errors and return actions to dispatch * Parse the submission object retrieved from REST and return actions to dispatch
* *
* @param currentState * @param currentState
* The current SubmissionObjectEntry * The current SubmissionObjectEntry

View File

@@ -138,7 +138,7 @@ export interface SubmissionObjectEntry {
collection?: string, collection?: string,
/** /**
* The configuration name tha define this submission * The configuration name that define this submission
*/ */
definition?: string, definition?: string,

View File

@@ -3,29 +3,64 @@ import { Component, Injector, Input, OnInit, ViewChild } from '@angular/core';
import { SectionsDirective } from '../sections.directive'; import { SectionsDirective } from '../sections.directive';
import { SectionDataObject } from '../models/section-data.model'; import { SectionDataObject } from '../models/section-data.model';
import { rendersSectionType } from '../sections-decorator'; import { rendersSectionType } from '../sections-decorator';
import { SectionsType } from '../sections-type';
import { AlertType } from '../../../shared/alerts/aletrs-type'; import { AlertType } from '../../../shared/alerts/aletrs-type';
/**
* This component represents a section that contains the submission license form.
*/
@Component({ @Component({
selector: 'ds-submission-form-section-container', selector: 'ds-submission-form-section-container',
templateUrl: './section-container.component.html', templateUrl: './section-container.component.html',
styleUrls: ['./section-container.component.scss'] styleUrls: ['./section-container.component.scss']
}) })
export class SectionContainerComponent implements OnInit { export class SectionContainerComponent implements OnInit {
/**
* The collection id this submission belonging to
* @type {string}
*/
@Input() collectionId: string; @Input() collectionId: string;
/**
* The section data
* @type {SectionDataObject}
*/
@Input() sectionData: SectionDataObject; @Input() sectionData: SectionDataObject;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string; @Input() submissionId: string;
/**
* The AlertType enumeration
* @type {AlertType}
*/
public AlertTypeEnum = AlertType; public AlertTypeEnum = AlertType;
public active = true;
public objectInjector: Injector;
public sectionComponentType: SectionsType;
/**
* Injector to inject a section component with the @Input parameters
* @type {Injector}
*/
public objectInjector: Injector;
/**
* The SectionsDirective reference
*/
@ViewChild('sectionRef') sectionRef: SectionsDirective; @ViewChild('sectionRef') sectionRef: SectionsDirective;
/**
* Initialize instance variables
*
* @param {Injector} injector
*/
constructor(private injector: Injector) { constructor(private injector: Injector) {
} }
/**
* Initialize all instance variables
*/
ngOnInit() { ngOnInit() {
this.objectInjector = Injector.create({ this.objectInjector = Injector.create({
providers: [ providers: [
@@ -37,12 +72,21 @@ export class SectionContainerComponent implements OnInit {
}); });
} }
/**
* Remove section from submission form
*
* @param event
* the event emitted
*/
public removeSection(event) { public removeSection(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.sectionRef.removeSection(this.submissionId, this.sectionData.id); this.sectionRef.removeSection(this.submissionId, this.sectionData.id);
} }
/**
* Find the correct component based on the section's type
*/
getSectionContent(): string { getSectionContent(): string {
return rendersSectionType(this.sectionData.sectionType); return rendersSectionType(this.sectionData.sectionType);
} }

View File

@@ -156,7 +156,7 @@ describe('SectionFormOperationsService test suite', () => {
} }
}; };
expect(service.isPartOfArrayOfGroup(model)).toBeTruthy(); expect(service.isPartOfArrayOfGroup(model as any)).toBeTruthy();
}); });
it('should return false when parent element doesn\'t belong to an array group element', () => { it('should return false when parent element doesn\'t belong to an array group element', () => {
@@ -164,7 +164,7 @@ describe('SectionFormOperationsService test suite', () => {
parent: null parent: null
}; };
expect(service.isPartOfArrayOfGroup(model)).toBeFalsy(); expect(service.isPartOfArrayOfGroup(model as any)).toBeFalsy();
}); });
}); });

View File

@@ -21,12 +21,35 @@ import { FormFieldMetadataValueObject } from '../../../shared/form/builder/model
import { DynamicQualdropModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model'; import { DynamicQualdropModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
/**
* The service handling all form section operations
*/
@Injectable() @Injectable()
export class SectionFormOperationsService { export class SectionFormOperationsService {
constructor(private formBuilder: FormBuilderService, private operationsBuilder: JsonPatchOperationsBuilder) { /**
* Initialize service variables
*
* @param {FormBuilderService} formBuilder
* @param {JsonPatchOperationsBuilder} operationsBuilder
*/
constructor(
private formBuilder: FormBuilderService,
private operationsBuilder: JsonPatchOperationsBuilder) {
} }
/**
* Dispatch properly method based on form operation type
*
* @param pathCombiner
* the [[JsonPatchOperationPathCombiner]] object for the specified operation
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @param previousValue
* the [[FormFieldPreviousValueObject]] for the specified operation
* @param hasStoredValue
* representing if field value related to the specified operation has stored value
*/
public dispatchOperationsFromEvent(pathCombiner: JsonPatchOperationPathCombiner, public dispatchOperationsFromEvent(pathCombiner: JsonPatchOperationPathCombiner,
event: DynamicFormControlEvent, event: DynamicFormControlEvent,
previousValue: FormFieldPreviousValueObject, previousValue: FormFieldPreviousValueObject,
@@ -43,6 +66,14 @@ export class SectionFormOperationsService {
} }
} }
/**
* Return index if specified field is part of fields array
*
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @return number
* the array index is part of array, zero otherwise
*/
public getArrayIndexFromEvent(event: DynamicFormControlEvent): number { public getArrayIndexFromEvent(event: DynamicFormControlEvent): number {
let fieldIndex: number; let fieldIndex: number;
if (isNotEmpty(event)) { if (isNotEmpty(event)) {
@@ -60,7 +91,15 @@ export class SectionFormOperationsService {
return isNotUndefined(fieldIndex) ? fieldIndex : 0; return isNotUndefined(fieldIndex) ? fieldIndex : 0;
} }
public isPartOfArrayOfGroup(model: any): boolean { /**
* Check if specified model is part of array of group
*
* @param model
* the [[DynamicFormControlModel]] model
* @return boolean
* true if is part of array, false otherwise
*/
public isPartOfArrayOfGroup(model: DynamicFormControlModel): boolean {
return (isNotNull(model.parent) return (isNotNull(model.parent)
&& (model.parent as any).type === DYNAMIC_FORM_CONTROL_TYPE_GROUP && (model.parent as any).type === DYNAMIC_FORM_CONTROL_TYPE_GROUP
&& (model.parent as any).parent && (model.parent as any).parent
@@ -68,7 +107,15 @@ export class SectionFormOperationsService {
&& (model.parent as any).parent.context.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY); && (model.parent as any).parent.context.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY);
} }
public getQualdropValueMap(event): Map<string, any> { /**
* Return a map for the values of a Qualdrop field
*
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @return Map<string, any>
* the map of values
*/
public getQualdropValueMap(event: DynamicFormControlEvent): Map<string, any> {
const metadataValueMap = new Map(); const metadataValueMap = new Map();
const context = this.formBuilder.isQualdropGroup(event.model) const context = this.formBuilder.isQualdropGroup(event.model)
@@ -87,12 +134,28 @@ export class SectionFormOperationsService {
return metadataValueMap; return metadataValueMap;
} }
/**
* Return the absolute path for the field interesting in the specified operation
*
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @return string
* the field path
*/
public getFieldPathFromEvent(event: DynamicFormControlEvent): string { public getFieldPathFromEvent(event: DynamicFormControlEvent): string {
const fieldIndex = this.getArrayIndexFromEvent(event); const fieldIndex = this.getArrayIndexFromEvent(event);
const fieldId = this.getFieldPathSegmentedFromChangeEvent(event); const fieldId = this.getFieldPathSegmentedFromChangeEvent(event);
return (isNotUndefined(fieldIndex)) ? fieldId + '/' + fieldIndex : fieldId; return (isNotUndefined(fieldIndex)) ? fieldId + '/' + fieldIndex : fieldId;
} }
/**
* Return the absolute path for the Qualdrop field interesting in the specified operation
*
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @return string
* the field path
*/
public getQualdropItemPathFromEvent(event: DynamicFormControlEvent): string { public getQualdropItemPathFromEvent(event: DynamicFormControlEvent): string {
const fieldIndex = this.getArrayIndexFromEvent(event); const fieldIndex = this.getArrayIndexFromEvent(event);
const metadataValueMap = new Map(); const metadataValueMap = new Map();
@@ -117,6 +180,14 @@ export class SectionFormOperationsService {
return path; return path;
} }
/**
* Return the segmented path for the field interesting in the specified change operation
*
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @return string
* the field path
*/
public getFieldPathSegmentedFromChangeEvent(event: DynamicFormControlEvent): string { public getFieldPathSegmentedFromChangeEvent(event: DynamicFormControlEvent): string {
let fieldId; let fieldId;
if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) { if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) {
@@ -129,6 +200,14 @@ export class SectionFormOperationsService {
return fieldId; return fieldId;
} }
/**
* Return the value of the field interesting in the specified change operation
*
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @return any
* the field value
*/
public getFieldValueFromChangeEvent(event: DynamicFormControlEvent): any { public getFieldValueFromChangeEvent(event: DynamicFormControlEvent): any {
let fieldValue; let fieldValue;
const value = (event.model as any).value; const value = (event.model as any).value;
@@ -142,12 +221,12 @@ export class SectionFormOperationsService {
if ((event.model as DsDynamicInputModel).hasAuthority) { if ((event.model as DsDynamicInputModel).hasAuthority) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((authority, index) => { value.forEach((authority, index) => {
authority = Object.assign(new AuthorityValue(), authority, {language}); authority = Object.assign(new AuthorityValue(), authority, { language });
value[index] = authority; value[index] = authority;
}); });
fieldValue = value; fieldValue = value;
} else { } else {
fieldValue = Object.assign(new AuthorityValue(), value, {language}); fieldValue = Object.assign(new AuthorityValue(), value, { language });
} }
} else { } else {
// Language without Authority (input, textArea) // Language without Authority (input, textArea)
@@ -162,6 +241,14 @@ export class SectionFormOperationsService {
return fieldValue; return fieldValue;
} }
/**
* Return a map for the values of an array of field
*
* @param items
* the list of items
* @return Map<string, any>
* the map of values
*/
public getValueMap(items: any[]): Map<string, any> { public getValueMap(items: any[]): Map<string, any> {
const metadataValueMap = new Map(); const metadataValueMap = new Map();
@@ -177,6 +264,16 @@ export class SectionFormOperationsService {
return metadataValueMap; return metadataValueMap;
} }
/**
* Handle form remove operations
*
* @param pathCombiner
* the [[JsonPatchOperationPathCombiner]] object for the specified operation
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @param previousValue
* the [[FormFieldPreviousValueObject]] for the specified operation
*/
protected dispatchOperationsFromRemoveEvent(pathCombiner: JsonPatchOperationPathCombiner, protected dispatchOperationsFromRemoveEvent(pathCombiner: JsonPatchOperationPathCombiner,
event: DynamicFormControlEvent, event: DynamicFormControlEvent,
previousValue: FormFieldPreviousValueObject): void { previousValue: FormFieldPreviousValueObject): void {
@@ -189,6 +286,18 @@ export class SectionFormOperationsService {
} }
} }
/**
* Handle form change operations
*
* @param pathCombiner
* the [[JsonPatchOperationPathCombiner]] object for the specified operation
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @param previousValue
* the [[FormFieldPreviousValueObject]] for the specified operation
* @param hasStoredValue
* representing if field value related to the specified operation has stored value
*/
protected dispatchOperationsFromChangeEvent(pathCombiner: JsonPatchOperationPathCombiner, protected dispatchOperationsFromChangeEvent(pathCombiner: JsonPatchOperationPathCombiner,
event: DynamicFormControlEvent, event: DynamicFormControlEvent,
previousValue: FormFieldPreviousValueObject, previousValue: FormFieldPreviousValueObject,
@@ -243,6 +352,18 @@ export class SectionFormOperationsService {
} }
} }
/**
* Handle form operations interesting a field with a map as value
*
* @param valueMap
* map of values
* @param pathCombiner
* the [[JsonPatchOperationPathCombiner]] object for the specified operation
* @param event
* the [[DynamicFormControlEvent]] for the specified operation
* @param previousValue
* the [[FormFieldPreviousValueObject]] for the specified operation
*/
protected dispatchOperationsFromMap(valueMap: Map<string, any>, protected dispatchOperationsFromMap(valueMap: Map<string, any>,
pathCombiner: JsonPatchOperationPathCombiner, pathCombiner: JsonPatchOperationPathCombiner,
event: DynamicFormControlEvent, event: DynamicFormControlEvent,

View File

@@ -42,46 +42,55 @@ export class FormSectionComponent extends SectionModelComponent {
/** /**
* The form id * The form id
* @type {string}
*/ */
public formId; public formId: string;
/** /**
* The form model * The form model
* @type {DynamicFormControlModel[]}
*/ */
public formModel: DynamicFormControlModel[]; public formModel: DynamicFormControlModel[];
/** /**
* A boolean representing if this section is updating * A boolean representing if this section is updating
* @type {boolean}
*/ */
public isUpdating = false; public isUpdating = false;
/** /**
* A boolean representing if this section is loading * A boolean representing if this section is loading
* @type {boolean}
*/ */
public isLoading = true; public isLoading = true;
/** /**
* The form config * The form config
* @type {SubmissionFormsModel}
*/ */
protected formConfig: SubmissionFormsModel; protected formConfig: SubmissionFormsModel;
/** /**
* The form data * The form data
* @type {any}
*/ */
protected formData: any = Object.create({}); protected formData: any = Object.create({});
/** /**
* The [JsonPatchOperationPathCombiner] object * The [JsonPatchOperationPathCombiner] object
* @type {JsonPatchOperationPathCombiner}
*/ */
protected pathCombiner: JsonPatchOperationPathCombiner; protected pathCombiner: JsonPatchOperationPathCombiner;
/** /**
* The [FormFieldPreviousValueObject] object * The [FormFieldPreviousValueObject] object
* @type {FormFieldPreviousValueObject}
*/ */
protected previousValue: FormFieldPreviousValueObject = new FormFieldPreviousValueObject(); protected previousValue: FormFieldPreviousValueObject = new FormFieldPreviousValueObject();
/** /**
* The list of Subscription * The list of Subscription
* @type {Array}
*/ */
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
@@ -92,6 +101,7 @@ export class FormSectionComponent extends SectionModelComponent {
/** /**
* Initialize instance variables * Initialize instance variables
*
* @param {ChangeDetectorRef} cdr * @param {ChangeDetectorRef} cdr
* @param {FormBuilderService} formBuilderService * @param {FormBuilderService} formBuilderService
* @param {SectionFormOperationsService} formOperationsService * @param {SectionFormOperationsService} formOperationsService
@@ -158,6 +168,9 @@ export class FormSectionComponent extends SectionModelComponent {
/** /**
* Get section status * Get section status
*
* @return Observable<boolean>
* the section status
*/ */
protected getSectionStatus(): Observable<boolean> { protected getSectionStatus(): Observable<boolean> {
return this.formService.isValid(this.formId); return this.formService.isValid(this.formId);
@@ -290,6 +303,9 @@ export class FormSectionComponent extends SectionModelComponent {
/** /**
* Method called when a form dfChange event is fired. * Method called when a form dfChange event is fired.
* Dispatch form operations based on changes. * Dispatch form operations based on changes.
*
* @param event
* the [[DynamicFormControlEvent]] emitted
*/ */
onChange(event: DynamicFormControlEvent): void { onChange(event: DynamicFormControlEvent): void {
this.formOperationsService.dispatchOperationsFromEvent( this.formOperationsService.dispatchOperationsFromEvent(
@@ -308,6 +324,9 @@ export class FormSectionComponent extends SectionModelComponent {
/** /**
* Method called when a form dfFocus event is fired. * Method called when a form dfFocus event is fired.
* Initialize [FormFieldPreviousValueObject] instance. * Initialize [FormFieldPreviousValueObject] instance.
*
* @param event
* the [[DynamicFormControlEvent]] emitted
*/ */
onFocus(event: DynamicFormControlEvent): void { onFocus(event: DynamicFormControlEvent): void {
const value = this.formOperationsService.getFieldValueFromChangeEvent(event); const value = this.formOperationsService.getFieldValueFromChangeEvent(event);
@@ -324,6 +343,9 @@ export class FormSectionComponent extends SectionModelComponent {
/** /**
* Method called when a form remove event is fired. * Method called when a form remove event is fired.
* Dispatch form operations based on changes. * Dispatch form operations based on changes.
*
* @param event
* the [[DynamicFormControlEvent]] emitted
*/ */
onRemove(event: DynamicFormControlEvent): void { onRemove(event: DynamicFormControlEvent): void {
this.formOperationsService.dispatchOperationsFromEvent( this.formOperationsService.dispatchOperationsFromEvent(

View File

@@ -29,6 +29,9 @@ import { SectionsService } from '../sections.service';
import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { SectionFormOperationsService } from '../form/section-form-operations.service';
import { FormComponent } from '../../../shared/form/form.component'; import { FormComponent } from '../../../shared/form/form.component';
/**
* This component represents a section that contains the submission license form.
*/
@Component({ @Component({
selector: 'ds-submission-section-license', selector: 'ds-submission-section-license',
styleUrls: ['./section-license.component.scss'], styleUrls: ['./section-license.component.scss'],
@@ -37,17 +40,68 @@ import { FormComponent } from '../../../shared/form/form.component';
@renderSectionFor(SectionsType.License) @renderSectionFor(SectionsType.License)
export class LicenseSectionComponent extends SectionModelComponent { export class LicenseSectionComponent extends SectionModelComponent {
public formId; /**
* The form id
* @type {string}
*/
public formId: string;
/**
* The form model
* @type {DynamicFormControlModel[]}
*/
public formModel: DynamicFormControlModel[]; public formModel: DynamicFormControlModel[];
/**
* The [[DynamicFormLayout]] object
* @type {DynamicFormLayout}
*/
public formLayout: DynamicFormLayout = SECTION_LICENSE_FORM_LAYOUT; public formLayout: DynamicFormLayout = SECTION_LICENSE_FORM_LAYOUT;
/**
* A boolean representing if to show form submit and cancel buttons
* @type {boolean}
*/
public displaySubmit = false; public displaySubmit = false;
/**
* The submission license text
* @type {Array}
*/
public licenseText$: Observable<string>; public licenseText$: Observable<string>;
/**
* The [[JsonPatchOperationPathCombiner]] object
* @type {JsonPatchOperationPathCombiner}
*/
protected pathCombiner: JsonPatchOperationPathCombiner; protected pathCombiner: JsonPatchOperationPathCombiner;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
/**
* The FormComponent reference
*/
@ViewChild('formRef') private formRef: FormComponent; @ViewChild('formRef') private formRef: FormComponent;
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} changeDetectorRef
* @param {CollectionDataService} collectionDataService
* @param {FormBuilderService} formBuilderService
* @param {SectionFormOperationsService} formOperationsService
* @param {FormService} formService
* @param {JsonPatchOperationsBuilder} operationsBuilder
* @param {SectionsService} sectionService
* @param {SubmissionService} submissionService
* @param {string} injectedCollectionId
* @param {SectionDataObject} injectedSectionData
* @param {string} injectedSubmissionId
*/
constructor(protected changeDetectorRef: ChangeDetectorRef, constructor(protected changeDetectorRef: ChangeDetectorRef,
protected collectionDataService: CollectionDataService, protected collectionDataService: CollectionDataService,
protected formBuilderService: FormBuilderService, protected formBuilderService: FormBuilderService,
@@ -62,6 +116,9 @@ export class LicenseSectionComponent extends SectionModelComponent {
super(injectedCollectionId, injectedSectionData, injectedSubmissionId); super(injectedCollectionId, injectedSectionData, injectedSubmissionId);
} }
/**
* Initialize all instance variables and retrieve submission license
*/
onSectionInit() { onSectionInit() {
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id);
this.formId = this.formService.getUniqueId(this.sectionData.id); this.formId = this.formService.getUniqueId(this.sectionData.id);
@@ -126,6 +183,12 @@ export class LicenseSectionComponent extends SectionModelComponent {
); );
} }
/**
* Get section status
*
* @return Observable<boolean>
* the section status
*/
protected getSectionStatus(): Observable<boolean> { protected getSectionStatus(): Observable<boolean> {
const model = this.formBuilderService.findById('granted', this.formModel); const model = this.formBuilderService.findById('granted', this.formModel);
return (model as DynamicCheckboxModel).valueUpdates.pipe( return (model as DynamicCheckboxModel).valueUpdates.pipe(
@@ -133,6 +196,10 @@ export class LicenseSectionComponent extends SectionModelComponent {
startWith((model as DynamicCheckboxModel).value)); startWith((model as DynamicCheckboxModel).value));
} }
/**
* Method called when a form dfChange event is fired.
* Dispatch form operations based on changes.
*/
onChange(event: DynamicFormControlEvent) { onChange(event: DynamicFormControlEvent) {
const path = this.formOperationsService.getFieldPathSegmentedFromChangeEvent(event); const path = this.formOperationsService.getFieldPathSegmentedFromChangeEvent(event);
const value = this.formOperationsService.getFieldValueFromChangeEvent(event); const value = this.formOperationsService.getFieldValueFromChangeEvent(event);
@@ -145,6 +212,9 @@ export class LicenseSectionComponent extends SectionModelComponent {
} }
} }
/**
* Unsubscribe from all subscriptions
*/
onSectionDestroy() { onSectionDestroy() {
this.subs this.subs
.filter((subscription) => hasValue(subscription)) .filter((subscription) => hasValue(subscription))

View File

@@ -2,14 +2,48 @@ import { SubmissionSectionError } from '../../objects/submission-objects.reducer
import { WorkspaceitemSectionDataType } from '../../../core/submission/models/workspaceitem-sections.model'; import { WorkspaceitemSectionDataType } from '../../../core/submission/models/workspaceitem-sections.model';
import { SectionsType } from '../sections-type'; import { SectionsType } from '../sections-type';
/**
* An interface to represent section model
*/
export interface SectionDataObject { export interface SectionDataObject {
/**
* The section configuration url
*/
config: string; config: string;
/**
* The section data object
*/
data: WorkspaceitemSectionDataType; data: WorkspaceitemSectionDataType;
/**
* The list of the section errors
*/
errors: SubmissionSectionError[]; errors: SubmissionSectionError[];
/**
* The section header
*/
header: string; header: string;
/**
* The section id
*/
id: string; id: string;
/**
* A boolean representing if this section is mandatory
*/
mandatory: boolean; mandatory: boolean;
/**
* The section type
*/
sectionType: SectionsType; sectionType: SectionsType;
/**
* Eventually additional fields
*/
[propName: string]: any; [propName: string]: any;
} }

View File

@@ -16,12 +16,44 @@ export interface SectionDataModel {
*/ */
export abstract class SectionModelComponent implements OnDestroy, OnInit, SectionDataModel { export abstract class SectionModelComponent implements OnDestroy, OnInit, SectionDataModel {
protected abstract sectionService: SectionsService; protected abstract sectionService: SectionsService;
/**
* The collection id this submission belonging to
* @type {string}
*/
collectionId: string; collectionId: string;
/**
* The section data
* @type {SectionDataObject}
*/
sectionData: SectionDataObject; sectionData: SectionDataObject;
/**
* The submission id
* @type {string}
*/
submissionId: string; submissionId: string;
/**
* A boolean representing if this section is valid
* @type {boolean}
*/
protected valid: boolean; protected valid: boolean;
/**
* The Subscription to section status observable
* @type {Subscription}
*/
private sectionStatusSub: Subscription; private sectionStatusSub: Subscription;
/**
* Initialize instance variables
*
* @param {string} injectedCollectionId
* @param {SectionDataObject} injectedSectionData
* @param {string} injectedSubmissionId
*/
public constructor(@Inject('collectionIdProvider') public injectedCollectionId: string, public constructor(@Inject('collectionIdProvider') public injectedCollectionId: string,
@Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject,
@Inject('submissionIdProvider') public injectedSubmissionId: string) { @Inject('submissionIdProvider') public injectedSubmissionId: string) {
@@ -30,15 +62,43 @@ export abstract class SectionModelComponent implements OnDestroy, OnInit, Sectio
this.submissionId = injectedSubmissionId; this.submissionId = injectedSubmissionId;
} }
/**
* Call abstract methods on component init
*/
ngOnInit(): void { ngOnInit(): void {
this.onSectionInit(); this.onSectionInit();
this.updateSectionStatus(); this.updateSectionStatus();
} }
/**
* Abstract method to implement to get section status
*
* @return Observable<boolean>
* the section status
*/
protected abstract getSectionStatus(): Observable<boolean>; protected abstract getSectionStatus(): Observable<boolean>;
/**
* Abstract method called on component init.
* It must be used instead of ngOnInit on the component that extend this abstract class
*
* @return Observable<boolean>
* the section status
*/
protected abstract onSectionInit(): void; protected abstract onSectionInit(): void;
/**
* Abstract method called on component destroy.
* It must be used instead of ngOnDestroy on the component that extend this abstract class
*
* @return Observable<boolean>
* the section status
*/
protected abstract onSectionDestroy(): void; protected abstract onSectionDestroy(): void;
/**
* Subscribe to section status
*/
protected updateSectionStatus(): void { protected updateSectionStatus(): void {
this.sectionStatusSub = this.getSectionStatus().pipe( this.sectionStatusSub = this.getSectionStatus().pipe(
filter((sectionStatus: boolean) => isNotUndefined(sectionStatus)), filter((sectionStatus: boolean) => isNotUndefined(sectionStatus)),
@@ -48,6 +108,9 @@ export abstract class SectionModelComponent implements OnDestroy, OnInit, Sectio
}); });
} }
/**
* Unsubscribe from all subscriptions and Call abstract methods on component destroy
*/
ngOnDestroy(): void { ngOnDestroy(): void {
if (hasValue(this.sectionStatusSub)) { if (hasValue(this.sectionStatusSub)) {
this.sectionStatusSub.unsubscribe(); this.sectionStatusSub.unsubscribe();

View File

@@ -10,28 +10,90 @@ import { SubmissionSectionError, SubmissionSectionObject } from '../objects/subm
import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionErrorPaths'; import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionErrorPaths';
import { SubmissionService } from '../submission.service'; import { SubmissionService } from '../submission.service';
/**
* Directive for handling generic section functionality
*/
@Directive({ @Directive({
selector: '[dsSection]', selector: '[dsSection]',
exportAs: 'sectionRef' exportAs: 'sectionRef'
}) })
export class SectionsDirective implements OnDestroy, OnInit { export class SectionsDirective implements OnDestroy, OnInit {
/**
* A boolean representing if section is mandatory
* @type {boolean}
*/
@Input() mandatory = true; @Input() mandatory = true;
@Input() sectionId;
@Input() submissionId; /**
* The section id
* @type {string}
*/
@Input() sectionId: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string;
/**
* The list of generic errors related to the section
* @type {Array}
*/
public genericSectionErrors: string[] = []; public genericSectionErrors: string[] = [];
/**
* The list of all errors related to the element belonging to this section
* @type {Array}
*/
public allSectionErrors: string[] = []; public allSectionErrors: string[] = [];
/**
* A boolean representing if section is active
* @type {boolean}
*/
private active = true; private active = true;
private animation = !this.mandatory;
/**
* A boolean representing if section is enabled
* @type {boolean}
*/
private enabled: Observable<boolean>; private enabled: Observable<boolean>;
/**
* A boolean representing the panel collapsible state: opened (true) or closed (false)
* @type {boolean}
*/
private sectionState = this.mandatory; private sectionState = this.mandatory;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
private subs: Subscription[] = []; private subs: Subscription[] = [];
/**
* A boolean representing if section is valid
* @type {boolean}
*/
private valid: Observable<boolean>; private valid: Observable<boolean>;
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} changeDetectorRef
* @param {SubmissionService} submissionService
* @param {SectionsService} sectionService
*/
constructor(private changeDetectorRef: ChangeDetectorRef, constructor(private changeDetectorRef: ChangeDetectorRef,
private submissionService: SubmissionService, private submissionService: SubmissionService,
private sectionService: SectionsService) { private sectionService: SectionsService) {
} }
/**
* Initialize instance variables
*/
ngOnInit() { ngOnInit() {
this.valid = this.sectionService.isSectionValid(this.submissionId, this.sectionId).pipe( this.valid = this.sectionService.isSectionValid(this.submissionId, this.sectionId).pipe(
map((valid: boolean) => { map((valid: boolean) => {
@@ -78,67 +140,145 @@ export class SectionsDirective implements OnDestroy, OnInit {
this.enabled = this.sectionService.isSectionEnabled(this.submissionId, this.sectionId); this.enabled = this.sectionService.isSectionEnabled(this.submissionId, this.sectionId);
} }
/**
* Unsubscribe from all subscriptions
*/
ngOnDestroy() { ngOnDestroy() {
this.subs this.subs
.filter((subscription) => hasValue(subscription)) .filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe()); .forEach((subscription) => subscription.unsubscribe());
} }
/**
* Change section state
*
* @param event
* the event emitted
*/
public sectionChange(event) { public sectionChange(event) {
this.sectionState = event.nextState; this.sectionState = event.nextState;
} }
public isOpen() { /**
* Check if section panel is open
*
* @returns {boolean}
* Returns true when section panel is open
*/
public isOpen(): boolean {
return this.sectionState; return this.sectionState;
} }
public isMandatory() { /**
* Check if section is mandatory
*
* @returns {boolean}
* Returns true when section is mandatory
*/
public isMandatory(): boolean {
return this.mandatory; return this.mandatory;
} }
public isAnimationsActive() { /**
return this.animation; * Check if section panel is active
} *
* @returns {boolean}
* Returns true when section panel is active
*/
public isSectionActive(): boolean { public isSectionActive(): boolean {
return this.active; return this.active;
} }
/**
* Check if section is enabled
*
* @returns {Observable<boolean>}
* Emits true whenever section is enabled
*/
public isEnabled(): Observable<boolean> { public isEnabled(): Observable<boolean> {
return this.enabled; return this.enabled;
} }
/**
* Check if section is valid
*
* @returns {Observable<boolean>}
* Emits true whenever section is valid
*/
public isValid(): Observable<boolean> { public isValid(): Observable<boolean> {
return this.valid; return this.valid;
} }
public removeSection(submissionId, sectionId) { /**
* Remove section panel from submission form
*
* @param submissionId
* the submission id
* @param sectionId
* the section id
* @returns {Observable<boolean>}
* Emits true whenever section is valid
*/
public removeSection(submissionId: string, sectionId: string) {
this.sectionService.removeSection(submissionId, sectionId) this.sectionService.removeSection(submissionId, sectionId)
} }
public hasGenericErrors() { /**
* Check if section has only generic errors
*
* @returns {boolean}
* Returns true when section has only generic errors
*/
public hasGenericErrors(): boolean {
return this.genericSectionErrors && this.genericSectionErrors.length > 0 return this.genericSectionErrors && this.genericSectionErrors.length > 0
} }
public hasErrors() { /**
* Check if section has errors
*
* @returns {boolean}
* Returns true when section has errors
*/
public hasErrors(): boolean {
return (this.genericSectionErrors && this.genericSectionErrors.length > 0) || return (this.genericSectionErrors && this.genericSectionErrors.length > 0) ||
(this.allSectionErrors && this.allSectionErrors.length > 0) (this.allSectionErrors && this.allSectionErrors.length > 0)
} }
public getErrors() { /**
* Return section errors
*
* @returns {Array}
* Returns section errors list
*/
public getErrors(): string[] {
return this.genericSectionErrors; return this.genericSectionErrors;
} }
public setFocus(event) { /**
* Set form focus to this section panel
*
* @param event
* The event emitted
*/
public setFocus(event): void {
if (!this.active) { if (!this.active) {
this.submissionService.setActiveSection(this.submissionId, this.sectionId); this.submissionService.setActiveSection(this.submissionId, this.sectionId);
} }
} }
public removeError(index) { /**
* Remove error from list
*
* @param index
* The error array key
*/
public removeError(index): void {
this.genericSectionErrors.splice(index); this.genericSectionErrors.splice(index);
} }
/**
* Remove all errors from list
*/
public resetErrors() { public resetErrors() {
if (isNotEmpty(this.genericSectionErrors)) { if (isNotEmpty(this.genericSectionErrors)) {
this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionId); this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionId);

View File

@@ -8,18 +8,37 @@ import { isEmpty } from '../../../../shared/empty.util';
import { Group } from '../../../../core/eperson/models/group.model'; import { Group } from '../../../../core/eperson/models/group.model';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
/**
* This component represents a badge that describe an access condition
*/
@Component({ @Component({
selector: 'ds-section-upload-access-conditions', selector: 'ds-section-upload-access-conditions',
templateUrl: './section-upload-access-conditions.component.html', templateUrl: './section-upload-access-conditions.component.html',
}) })
export class SectionUploadAccessConditionsComponent implements OnInit { export class SectionUploadAccessConditionsComponent implements OnInit {
/**
* The list of resource policy
* @type {Array}
*/
@Input() accessConditions: ResourcePolicy[]; @Input() accessConditions: ResourcePolicy[];
/**
* The list of access conditions
* @type {Array}
*/
public accessConditionsList = []; public accessConditionsList = [];
/**
* Initialize instance variables
*
* @param {GroupEpersonService} groupService
*/
constructor(private groupService: GroupEpersonService) {} constructor(private groupService: GroupEpersonService) {}
/**
* Retrieve access conditions list
*/
ngOnInit() { ngOnInit() {
this.accessConditions.forEach((accessCondition: ResourcePolicy) => { this.accessConditions.forEach((accessCondition: ResourcePolicy) => {
if (isEmpty(accessCondition.name)) { if (isEmpty(accessCondition.name)) {

View File

@@ -1,6 +1,6 @@
import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model';
import { import {
DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER, DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER,
DynamicDateControlModel, DynamicDateControlModel,
@@ -12,6 +12,8 @@ import {
DynamicFormGroupModel, DynamicFormGroupModel,
DynamicSelectModel DynamicSelectModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model';
import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service'; import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
import { import {
BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG,
@@ -35,37 +37,113 @@ import { AccessConditionOption } from '../../../../../core/config/models/config-
import { SubmissionService } from '../../../../submission.service'; import { SubmissionService } from '../../../../submission.service';
import { FormService } from '../../../../../shared/form/form.service'; import { FormService } from '../../../../../shared/form/form.service';
import { FormComponent } from '../../../../../shared/form/form.component'; import { FormComponent } from '../../../../../shared/form/form.component';
import { FormControl } from '@angular/forms';
import { Group } from '../../../../../core/eperson/models/group.model'; import { Group } from '../../../../../core/eperson/models/group.model';
/**
* This component represents the edit form for bitstream
*/
@Component({ @Component({
selector: 'ds-submission-upload-section-file-edit', selector: 'ds-submission-upload-section-file-edit',
templateUrl: './section-upload-file-edit.component.html', templateUrl: './section-upload-file-edit.component.html',
}) })
export class UploadSectionFileEditComponent implements OnChanges { export class UploadSectionFileEditComponent implements OnChanges {
/**
* The list of available access condition
* @type {Array}
*/
@Input() availableAccessConditionOptions: any[]; @Input() availableAccessConditionOptions: any[];
@Input() availableAccessConditionGroups: Map<string, Group[]>;
@Input() collectionId;
@Input() collectionPolicyType;
@Input() configMetadataForm: SubmissionFormsModel;
@Input() fileData: WorkspaceitemSectionUploadFileObject;
@Input() fileId;
@Input() fileIndex;
@Input() formId;
@Input() sectionId;
@Input() submissionId;
/**
* The list of available groups for an access condition
* @type {Array}
*/
@Input() availableAccessConditionGroups: Map<string, Group[]>;
/**
* The submission id
* @type {string}
*/
@Input() collectionId: string;
/**
* Define if collection access conditions policy type :
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
* @type {number}
*/
@Input() collectionPolicyType: number;
/**
* The configuration for the bitstream's metadata form
* @type {SubmissionFormsModel}
*/
@Input() configMetadataForm: SubmissionFormsModel;
/**
* The bitstream's metadata data
* @type {WorkspaceitemSectionUploadFileObject}
*/
@Input() fileData: WorkspaceitemSectionUploadFileObject;
/**
* The bitstream id
* @type {string}
*/
@Input() fileId: string;
/**
* The bitstream array key
* @type {string}
*/
@Input() fileIndex: string;
/**
* The form id
* @type {string}
*/
@Input() formId: string;
/**
* The section id
* @type {string}
*/
@Input() sectionId: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string;
/**
* The form model
* @type {DynamicFormControlModel[]}
*/
public formModel: DynamicFormControlModel[]; public formModel: DynamicFormControlModel[];
/**
* The FormComponent reference
*/
@ViewChild('formRef') public formRef: FormComponent; @ViewChild('formRef') public formRef: FormComponent;
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} cdr
* @param {FormBuilderService} formBuilderService
* @param {FormService} formService
* @param {SubmissionService} submissionService
*/
constructor(private cdr: ChangeDetectorRef, constructor(private cdr: ChangeDetectorRef,
private formBuilderService: FormBuilderService, private formBuilderService: FormBuilderService,
private formService: FormService, private formService: FormService,
private submissionService: SubmissionService) { private submissionService: SubmissionService) {
} }
/**
* Dispatch form model init
*/
ngOnChanges() { ngOnChanges() {
if (this.fileData && this.formId) { if (this.fileData && this.formId) {
this.formModel = this.buildFileEditForm(); this.formModel = this.buildFileEditForm();
@@ -73,8 +151,10 @@ export class UploadSectionFileEditComponent implements OnChanges {
} }
} }
/**
* Initialize form model
*/
protected buildFileEditForm() { protected buildFileEditForm() {
// TODO check in the rest server configuration whether dc.description may be repeatable
const configDescr: FormFieldModel = Object.assign({}, this.configMetadataForm.rows[0].fields[0]); const configDescr: FormFieldModel = Object.assign({}, this.configMetadataForm.rows[0].fields[0]);
configDescr.repeatable = false; configDescr.repeatable = false;
const configForm = Object.assign({}, this.configMetadataForm, { const configForm = Object.assign({}, this.configMetadataForm, {
@@ -107,7 +187,7 @@ export class UploadSectionFileEditComponent implements OnChanges {
} }
accessConditionTypeModelConfig.options = accessConditionTypeOptions; accessConditionTypeModelConfig.options = accessConditionTypeOptions;
// Dynamic assign of relation in config. For startdate, endDate, groups. // Dynamically assign of relation in config. For startdate, endDate, groups.
const hasStart = []; const hasStart = [];
const hasEnd = []; const hasEnd = [];
const hasGroups = []; const hasGroups = [];
@@ -153,6 +233,12 @@ export class UploadSectionFileEditComponent implements OnChanges {
return formModel; return formModel;
} }
/**
* Initialize form model values
*
* @param formModel
* The form model
*/
public initModelData(formModel: DynamicFormControlModel[]) { public initModelData(formModel: DynamicFormControlModel[]) {
this.fileData.accessConditions.forEach((accessCondition, index) => { this.fileData.accessConditions.forEach((accessCondition, index) => {
Array.of('name', 'groupUUID', 'startDate', 'endDate') Array.of('name', 'groupUUID', 'startDate', 'endDate')
@@ -183,22 +269,36 @@ export class UploadSectionFileEditComponent implements OnChanges {
}); });
} }
/**
* Dispatch form model update when changing an access condition
*
* @param formModel
* The form model
*/
public onChange(event: DynamicFormControlEvent) { public onChange(event: DynamicFormControlEvent) {
if (event.model.id === 'name') { if (event.model.id === 'name') {
this.setOptions(event.model, event.control); this.setOptions(event.model, event.control);
} }
} }
public setOptions(model, control) { /**
* Update `startDate`, 'groupUUID' and 'endDate' model
*
* @param model
* The [[DynamicFormControlModel]] object
* @param control
* The [[FormControl]] object
*/
public setOptions(model: DynamicFormControlModel, control: FormControl) {
let accessCondition: AccessConditionOption = null; let accessCondition: AccessConditionOption = null;
this.availableAccessConditionOptions.filter((element) => element.name === control.value) this.availableAccessConditionOptions.filter((element) => element.name === control.value)
.forEach((element) => accessCondition = element); .forEach((element) => accessCondition = element);
if (isNotEmpty(accessCondition)) { if (isNotEmpty(accessCondition)) {
const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true; const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true;
const groupControl: FormControl = control.parent.get('groupUUID'); const groupControl: FormControl = control.parent.get('groupUUID') as FormControl;
const startDateControl: FormControl = control.parent.get('startDate'); const startDateControl: FormControl = control.parent.get('startDate') as FormControl;
const endDateControl: FormControl = control.parent.get('endDate'); const endDateControl: FormControl = control.parent.get('endDate') as FormControl;
// Clear previous state // Clear previous state
groupControl.markAsUntouched(); groupControl.markAsUntouched();
@@ -236,8 +336,8 @@ export class UploadSectionFileEditComponent implements OnChanges {
const confGroup = { relation: groupModel.relation }; const confGroup = { relation: groupModel.relation };
const groupsConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_CONFIG, confGroup); const groupsConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_CONFIG, confGroup);
groupsConfig.options = groupOptions; groupsConfig.options = groupOptions;
model.parent.group.pop(); (model.parent as DynamicFormGroupModel).group.pop();
model.parent.group.push(new DynamicSelectModel(groupsConfig, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT)); (model.parent as DynamicFormGroupModel).group.push(new DynamicSelectModel(groupsConfig, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT));
} }
} }

View File

@@ -1,6 +1,6 @@
import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject, Subscription } from 'rxjs';
import { filter, first, flatMap, take } from 'rxjs/operators'; import { filter, first, flatMap, take } from 'rxjs/operators';
import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { DynamicFormControlModel, } from '@ng-dynamic-forms/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@@ -23,6 +23,9 @@ import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/mo
import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component'; import { UploadSectionFileEditComponent } from './edit/section-upload-file-edit.component';
import { Group } from '../../../../core/eperson/models/group.model'; import { Group } from '../../../../core/eperson/models/group.model';
/**
* This component represents a single bitstream contained in the submission
*/
@Component({ @Component({
selector: 'ds-submission-upload-section-file', selector: 'ds-submission-upload-section-file',
styleUrls: ['./section-upload-file.component.scss'], styleUrls: ['./section-upload-file.component.scss'],
@@ -30,28 +33,129 @@ import { Group } from '../../../../core/eperson/models/group.model';
}) })
export class UploadSectionFileComponent implements OnChanges, OnInit { export class UploadSectionFileComponent implements OnChanges, OnInit {
/**
* The list of available access condition
* @type {Array}
*/
@Input() availableAccessConditionOptions: any[]; @Input() availableAccessConditionOptions: any[];
@Input() availableAccessConditionGroups: Map<string, Group[]>;
@Input() collectionId;
@Input() collectionPolicyType;
@Input() configMetadataForm: SubmissionFormsModel;
@Input() fileId;
@Input() fileIndex;
@Input() fileName;
@Input() sectionId;
@Input() submissionId;
/**
* The list of available groups for an access condition
* @type {Array}
*/
@Input() availableAccessConditionGroups: Map<string, Group[]>;
/**
* The submission id
* @type {string}
*/
@Input() collectionId: string;
/**
* Define if collection access conditions policy type :
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
* @type {number}
*/
@Input() collectionPolicyType: number;
/**
* The configuration for the bitstream's metadata form
* @type {SubmissionFormsModel}
*/
@Input() configMetadataForm: SubmissionFormsModel;
/**
* The bitstream id
* @type {string}
*/
@Input() fileId: string;
/**
* The bitstream array key
* @type {string}
*/
@Input() fileIndex: string;
/**
* The bitstream id
* @type {string}
*/
@Input() fileName: string;
/**
* The section id
* @type {string}
*/
@Input() sectionId: string;
/**
* The submission id
* @type {string}
*/
@Input() submissionId: string;
/**
* The bitstream's metadata data
* @type {WorkspaceitemSectionUploadFileObject}
*/
public fileData: WorkspaceitemSectionUploadFileObject; public fileData: WorkspaceitemSectionUploadFileObject;
public formId;
public readMode; /**
* The form id
* @type {string}
*/
public formId: string;
/**
* A boolean representing if to show bitstream edit form
* @type {boolean}
*/
public readMode: boolean;
/**
* The form model
* @type {DynamicFormControlModel[]}
*/
public formModel: DynamicFormControlModel[]; public formModel: DynamicFormControlModel[];
/**
* A boolean representing if a submission delete operation is pending
* @type {BehaviorSubject<boolean>}
*/
public processingDelete$ = new BehaviorSubject<boolean>(false); public processingDelete$ = new BehaviorSubject<boolean>(false);
/**
* The [JsonPatchOperationPathCombiner] object
* @type {JsonPatchOperationPathCombiner}
*/
protected pathCombiner: JsonPatchOperationPathCombiner; protected pathCombiner: JsonPatchOperationPathCombiner;
protected subscriptions = [];
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subscriptions: Subscription[] = [];
/**
* The [[UploadSectionFileEditComponent]] reference
* @type {UploadSectionFileEditComponent}
*/
@ViewChild(UploadSectionFileEditComponent) fileEditComp: UploadSectionFileEditComponent; @ViewChild(UploadSectionFileEditComponent) fileEditComp: UploadSectionFileEditComponent;
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} cdr
* @param {FileService} fileService
* @param {FormService} formService
* @param {HALEndpointService} halService
* @param {NgbModal} modalService
* @param {JsonPatchOperationsBuilder} operationsBuilder
* @param {SubmissionJsonPatchOperationsService} operationsService
* @param {SubmissionService} submissionService
* @param {SectionUploadService} uploadService
*/
constructor(private cdr: ChangeDetectorRef, constructor(private cdr: ChangeDetectorRef,
private fileService: FileService, private fileService: FileService,
private formService: FormService, private formService: FormService,
@@ -64,6 +168,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
this.readMode = true; this.readMode = true;
} }
/**
* Retrieve bitstream's metadata
*/
ngOnChanges() { ngOnChanges() {
if (this.availableAccessConditionOptions && this.availableAccessConditionGroups) { if (this.availableAccessConditionOptions && this.availableAccessConditionGroups) {
// Retrieve file state // Retrieve file state
@@ -79,11 +186,17 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
} }
} }
/**
* Initialize instance variables
*/
ngOnInit() { ngOnInit() {
this.formId = this.formService.getUniqueId(this.fileId); this.formId = this.formService.getUniqueId(this.fileId);
this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex);
} }
/**
* Delete bitstream from submission
*/
protected deleteFile() { protected deleteFile() {
this.operationsBuilder.remove(this.pathCombiner.getPath()); this.operationsBuilder.remove(this.pathCombiner.getPath());
this.subscriptions.push(this.operationsService.jsonPatchByResourceID( this.subscriptions.push(this.operationsService.jsonPatchByResourceID(
@@ -97,6 +210,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
})); }));
} }
/**
* Show confirmation dialog for delete
*/
public confirmDelete(content) { public confirmDelete(content) {
this.modalService.open(content).result.then( this.modalService.open(content).result.then(
(result) => { (result) => {
@@ -108,6 +224,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
); );
} }
/**
* Perform bitstream download
*/
public downloadBitstreamFile() { public downloadBitstreamFile() {
this.halService.getEndpoint('bitstreams').pipe( this.halService.getEndpoint('bitstreams').pipe(
first()) first())
@@ -117,9 +236,16 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
}); });
} }
/**
* Save bitstream metadata
*
* @param event
* the click event emitted
*/
public saveBitstreamData(event) { public saveBitstreamData(event) {
event.preventDefault(); event.preventDefault();
// validate form
this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup); this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup);
this.subscriptions.push(this.formService.isValid(this.formId).pipe( this.subscriptions.push(this.formService.isValid(this.formId).pipe(
take(1), take(1),
@@ -127,6 +253,7 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
flatMap(() => this.formService.getFormData(this.formId)), flatMap(() => this.formService.getFormData(this.formId)),
take(1), take(1),
flatMap((formData: any) => { flatMap((formData: any) => {
// collect bitstream metadata
Object.keys((formData.metadata)) Object.keys((formData.metadata))
.filter((key) => isNotEmpty(formData.metadata[key])) .filter((key) => isNotEmpty(formData.metadata[key]))
.forEach((key) => { .forEach((key) => {
@@ -174,6 +301,7 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true); this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true);
} }
// dispatch a PATCH request to save metadata
return this.operationsService.jsonPatchByResourceID( return this.operationsService.jsonPatchByResourceID(
this.submissionService.getSubmissionObjectLinkName(), this.submissionService.getSubmissionObjectLinkName(),
this.submissionId, this.submissionId,
@@ -194,11 +322,20 @@ export class UploadSectionFileComponent implements OnChanges, OnInit {
})); }));
} }
private retrieveValueFromField(field) { /**
* Retrieve field value
*
* @param field
* the specified field object
*/
private retrieveValueFromField(field: any) {
const temp = Array.isArray(field) ? field[0] : field; const temp = Array.isArray(field) ? field[0] : field;
return (temp) ? temp.value : undefined; return (temp) ? temp.value : undefined;
} }
/**
* Switch from edit form to metadata view
*/
public switchMode() { public switchMode() {
this.readMode = !this.readMode; this.readMode = !this.readMode;
this.cdr.detectChanges(); this.cdr.detectChanges();

View File

@@ -5,17 +5,42 @@ import { isNotEmpty } from '../../../../../shared/empty.util';
import { Metadata } from '../../../../../core/shared/metadata.utils'; import { Metadata } from '../../../../../core/shared/metadata.utils';
import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata.models'; import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata.models';
/**
* This component allow to show bitstream's metadata
*/
@Component({ @Component({
selector: 'ds-submission-upload-section-file-view', selector: 'ds-submission-upload-section-file-view',
templateUrl: './section-upload-file-view.component.html', templateUrl: './section-upload-file-view.component.html',
}) })
export class UploadSectionFileViewComponent implements OnInit { export class UploadSectionFileViewComponent implements OnInit {
/**
* The bitstream's metadata data
* @type {WorkspaceitemSectionUploadFileObject}
*/
@Input() fileData: WorkspaceitemSectionUploadFileObject; @Input() fileData: WorkspaceitemSectionUploadFileObject;
/**
* The [[MetadataMap]] object
* @type {MetadataMap}
*/
public metadata: MetadataMap = Object.create({}); public metadata: MetadataMap = Object.create({});
/**
* The bitstream's title key
* @type {string}
*/
public fileTitleKey = 'Title'; public fileTitleKey = 'Title';
/**
* The bitstream's description key
* @type {string}
*/
public fileDescrKey = 'Description'; public fileDescrKey = 'Description';
/**
* Initialize instance variables
*/
ngOnInit() { ngOnInit() {
if (isNotEmpty(this.fileData.metadata)) { if (isNotEmpty(this.fileData.metadata)) {
this.metadata[this.fileTitleKey] = Metadata.all(this.fileData.metadata, 'dc.title'); this.metadata[this.fileTitleKey] = Metadata.all(this.fileData.metadata, 'dc.title');
@@ -23,7 +48,15 @@ export class UploadSectionFileViewComponent implements OnInit {
} }
} }
getAllMetadataValue(metadataKey): MetadataValue[] { /**
* Gets all matching metadata in the map(s)
*
* @param metadataKey
* The metadata key(s) in scope
* @returns {MetadataValue[]}
* The matching values
*/
getAllMetadataValue(metadataKey: string): MetadataValue[] {
return Metadata.all(this.metadata, metadataKey); return Metadata.all(this.metadata, metadataKey);
} }
} }

View File

@@ -15,13 +15,10 @@
<div *ngIf="collectionDefaultAccessConditions.length > 0" class="row"> <div *ngIf="collectionDefaultAccessConditions.length > 0" class="row">
<div class="col-sm-12" > <div class="col-sm-12" >
<ds-alert [type]="AlertTypeEnum.Warning"> <ds-alert [type]="AlertTypeEnum.Warning">
<!-- no def , no banner -->
<ng-container *ngIf="collectionPolicyType === 1"> <ng-container *ngIf="collectionPolicyType === 1">
<!-- def e no scelta -->
{{ 'submission.sections.upload.header.policy.default.nolist' | translate:{ "collectionName": collectionName } }} {{ 'submission.sections.upload.header.policy.default.nolist' | translate:{ "collectionName": collectionName } }}
</ng-container> </ng-container>
<ng-container *ngIf="collectionPolicyType === 2"> <ng-container *ngIf="collectionPolicyType === 2">
<!-- def e scelta -->
{{ 'submission.sections.upload.header.policy.default.withlist' | translate:{ "collectionName": collectionName } }} {{ 'submission.sections.upload.header.policy.default.withlist' | translate:{ "collectionName": collectionName } }}
</ng-container> </ng-container>
<span class="clearfix"></span> <span class="clearfix"></span>

View File

@@ -1,6 +1,6 @@
import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { combineLatest, Observable } from 'rxjs'; import { combineLatest, Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators';
import { SectionModelComponent } from '../models/section.model'; import { SectionModelComponent } from '../models/section.model';
@@ -33,6 +33,9 @@ export interface AccessConditionGroupsMapEntry {
groups: Group[] groups: Group[]
} }
/**
* This component represents a section that contains submission's bitstreams
*/
@Component({ @Component({
selector: 'ds-submission-section-upload', selector: 'ds-submission-section-upload',
styleUrls: ['./section-upload.component.scss'], styleUrls: ['./section-upload.component.scss'],
@@ -41,37 +44,84 @@ export interface AccessConditionGroupsMapEntry {
@renderSectionFor(SectionsType.Upload) @renderSectionFor(SectionsType.Upload)
export class UploadSectionComponent extends SectionModelComponent { export class UploadSectionComponent extends SectionModelComponent {
/**
* The AlertType enumeration
* @type {AlertType}
*/
public AlertTypeEnum = AlertType; public AlertTypeEnum = AlertType;
public fileIndexes = [];
public fileList = [];
public fileNames = [];
/**
* The array containing the keys of file list array
* @type {Array}
*/
public fileIndexes: string[] = [];
/**
* The file list
* @type {Array}
*/
public fileList: any[] = [];
/**
* The array containing the name of the files
* @type {Array}
*/
public fileNames: string[] = [];
/**
* The collection name this submission belonging to
* @type {string}
*/
public collectionName: string; public collectionName: string;
/* /**
* Default access conditions of this collection * Default access conditions of this collection
* @type {Array}
*/ */
public collectionDefaultAccessConditions: any[] = []; public collectionDefaultAccessConditions: any[] = [];
/* /**
* The collection access conditions policy * Define if collection access conditions policy type :
* POLICY_DEFAULT_NO_LIST : is not possible to define additional access group/s for the single file
* POLICY_DEFAULT_WITH_LIST : is possible to define additional access group/s for the single file
* @type {number}
*/ */
public collectionPolicyType; public collectionPolicyType: number;
/**
* The configuration for the bitstream's metadata form
*/
public configMetadataForm$: Observable<SubmissionFormsModel>; public configMetadataForm$: Observable<SubmissionFormsModel>;
/* /**
* List of available access conditions that could be setted to files * List of available access conditions that could be setted to files
*/ */
public availableAccessConditionOptions: AccessConditionOption[]; // List of accessConditions that an user can select public availableAccessConditionOptions: AccessConditionOption[]; // List of accessConditions that an user can select
/* /**
* List of Groups available for every access condition * List of Groups available for every access condition
*/ */
protected availableGroups: Map<string, Group[]>; // Groups for any policy protected availableGroups: Map<string, Group[]>; // Groups for any policy
protected subs = []; /**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = [];
/**
* Initialize instance variables
*
* @param {SectionUploadService} bitstreamService
* @param {ChangeDetectorRef} changeDetectorRef
* @param {CollectionDataService} collectionDataService
* @param {GroupEpersonService} groupService
* @param {SectionsService} sectionService
* @param {SubmissionService} submissionService
* @param {SubmissionUploadsConfigService} uploadsConfigService
* @param {SectionDataObject} injectedSectionData
* @param {string} injectedSubmissionId
*/
constructor(private bitstreamService: SectionUploadService, constructor(private bitstreamService: SectionUploadService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private collectionDataService: CollectionDataService, private collectionDataService: CollectionDataService,
@@ -84,10 +134,14 @@ export class UploadSectionComponent extends SectionModelComponent {
super(undefined, injectedSectionData, injectedSubmissionId); super(undefined, injectedSectionData, injectedSubmissionId);
} }
/**
* Initialize all instance variables and retrieve collection default access conditions
*/
onSectionInit() { onSectionInit() {
const config$ = this.uploadsConfigService.getConfigByHref(this.sectionData.config).pipe( const config$ = this.uploadsConfigService.getConfigByHref(this.sectionData.config).pipe(
map((config) => config.payload)); map((config) => config.payload));
// retrieve configuration for the bitstream's metadata form
this.configMetadataForm$ = config$.pipe( this.configMetadataForm$ = config$.pipe(
take(1), take(1),
map((config: SubmissionUploadsModel) => config.metadata)); map((config: SubmissionUploadsModel) => config.metadata));
@@ -124,7 +178,7 @@ export class UploadSectionComponent extends SectionModelComponent {
this.availableGroups = new Map(); this.availableGroups = new Map();
const mapGroups$: Array<Observable<AccessConditionGroupsMapEntry>> = []; const mapGroups$: Array<Observable<AccessConditionGroupsMapEntry>> = [];
// Retrieve Groups for accessConditionPolicies // Retrieve Groups for accessCondition Policies
this.availableAccessConditionOptions.forEach((accessCondition: AccessConditionOption) => { this.availableAccessConditionOptions.forEach((accessCondition: AccessConditionOption) => {
if (accessCondition.hasEndDate === true || accessCondition.hasStartDate === true) { if (accessCondition.hasEndDate === true || accessCondition.hasStartDate === true) {
if (accessCondition.groupUUID) { if (accessCondition.groupUUID) {
@@ -148,7 +202,7 @@ export class UploadSectionComponent extends SectionModelComponent {
accessCondition: accessCondition.name, accessCondition: accessCondition.name,
groups: rd.payload.page groups: rd.payload.page
} as AccessConditionGroupsMapEntry)) } as AccessConditionGroupsMapEntry))
)); ));
} }
} }
}); });
@@ -164,8 +218,9 @@ export class UploadSectionComponent extends SectionModelComponent {
this.availableGroups.set(entry.accessCondition, entry.groups); this.availableGroups.set(entry.accessCondition, entry.groups);
}); });
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
}) }),
,
// retrieve submission's bitstreams from state
combineLatest(this.configMetadataForm$, combineLatest(this.configMetadataForm$,
this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe( this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe(
filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => { filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
@@ -173,24 +228,32 @@ export class UploadSectionComponent extends SectionModelComponent {
}), }),
distinctUntilChanged()) distinctUntilChanged())
.subscribe(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => { .subscribe(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => {
this.fileList = []; this.fileList = [];
this.fileIndexes = []; this.fileIndexes = [];
this.fileNames = []; this.fileNames = [];
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
if (isNotUndefined(fileList) && fileList.length > 0) { if (isNotUndefined(fileList) && fileList.length > 0) {
fileList.forEach((file) => { fileList.forEach((file) => {
this.fileList.push(file); this.fileList.push(file);
this.fileIndexes.push(file.uuid); this.fileIndexes.push(file.uuid);
this.fileNames.push(this.getFileName(configMetadataForm, file)); this.fileNames.push(this.getFileName(configMetadataForm, file));
}); });
} }
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
} }
) )
); );
} }
/**
* Return file name from metadata
*
* @param configMetadataForm
* the bitstream's form configuration
* @param fileData
* the file metadata
*/
private getFileName(configMetadataForm: SubmissionFormsModel, fileData: any): string { private getFileName(configMetadataForm: SubmissionFormsModel, fileData: any): string {
const metadataName: string = configMetadataForm.rows[0].fields[0].selectableMetadata[0].metadata; const metadataName: string = configMetadataForm.rows[0].fields[0].selectableMetadata[0].metadata;
let title: string; let title: string;
@@ -203,6 +266,12 @@ export class UploadSectionComponent extends SectionModelComponent {
return title; return title;
} }
/**
* Get section status
*
* @return Observable<boolean>
* the section status
*/
protected getSectionStatus(): Observable<boolean> { protected getSectionStatus(): Observable<boolean> {
return this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id).pipe( return this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id).pipe(
map((fileList: any[]) => (isNotUndefined(fileList) && fileList.length > 0))); map((fileList: any[]) => (isNotUndefined(fileList) && fileList.length > 0)));

View File

@@ -14,51 +14,127 @@ import { submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSe
import { isUndefined } from '../../../shared/empty.util'; import { isUndefined } from '../../../shared/empty.util';
import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model'; import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model';
/**
* A service that provides methods to handle submission's bitstream state.
*/
@Injectable() @Injectable()
export class SectionUploadService { export class SectionUploadService {
/**
* Initialize service variables
*
* @param {Store<SubmissionState>} store
*/
constructor(private store: Store<SubmissionState>) {} constructor(private store: Store<SubmissionState>) {}
/**
* Return submission's bitstream list from state
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @returns {Array}
* Returns submission's bitstream list
*/
public getUploadedFileList(submissionId: string, sectionId: string): Observable<any> { public getUploadedFileList(submissionId: string, sectionId: string): Observable<any> {
return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe(
map((state) => state), map((state) => state),
distinctUntilChanged()); distinctUntilChanged());
} }
public getFileData(submissionId: string, sectionId: string, fileUuid: string): Observable<any> { /**
* Return bitstream's metadata
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param fileUUID
* The bitstream UUID
* @returns {Observable}
* Emits bitstream's metadata
*/
public getFileData(submissionId: string, sectionId: string, fileUUID: string): Observable<any> {
return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe( return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe(
filter((state) => !isUndefined(state)), filter((state) => !isUndefined(state)),
map((state) => { map((state) => {
let fileState; let fileState;
Object.keys(state) Object.keys(state)
.filter((key) => state[key].uuid === fileUuid) .filter((key) => state[key].uuid === fileUUID)
.forEach((key) => fileState = state[key]); .forEach((key) => fileState = state[key]);
return fileState; return fileState;
}), }),
distinctUntilChanged()); distinctUntilChanged());
} }
public getDefaultPolicies(submissionId: string, sectionId: string, fileId: string): Observable<any> { /**
return this.store.select(submissionUploadedFileFromUuidSelector(submissionId, sectionId, fileId)).pipe( * Return bitstream's default policies
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param fileUUID
* The bitstream UUID
* @returns {Observable}
* Emits bitstream's default policies
*/
public getDefaultPolicies(submissionId: string, sectionId: string, fileUUID: string): Observable<any> {
return this.store.select(submissionUploadedFileFromUuidSelector(submissionId, sectionId, fileUUID)).pipe(
map((state) => state), map((state) => state),
distinctUntilChanged()); distinctUntilChanged());
} }
public addUploadedFile(submissionId: string, sectionId: string, fileId: string, data: WorkspaceitemSectionUploadFileObject) { /**
* Add a new bitstream to the state
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param fileUUID
* The bitstream UUID
* @param data
* The [[WorkspaceitemSectionUploadFileObject]] object
*/
public addUploadedFile(submissionId: string, sectionId: string, fileUUID: string, data: WorkspaceitemSectionUploadFileObject) {
this.store.dispatch( this.store.dispatch(
new NewUploadedFileAction(submissionId, sectionId, fileId, data) new NewUploadedFileAction(submissionId, sectionId, fileUUID, data)
); );
} }
public updateFileData(submissionId: string, sectionId: string, fileId: string, data: WorkspaceitemSectionUploadFileObject) { /**
* Update bitstream metadata into the state
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param fileUUID
* The bitstream UUID
* @param data
* The [[WorkspaceitemSectionUploadFileObject]] object
*/
public updateFileData(submissionId: string, sectionId: string, fileUUID: string, data: WorkspaceitemSectionUploadFileObject) {
this.store.dispatch( this.store.dispatch(
new EditFileDataAction(submissionId, sectionId, fileId, data) new EditFileDataAction(submissionId, sectionId, fileUUID, data)
); );
} }
public removeUploadedFile(submissionId: string, sectionId: string, fileId: string) { /**
* Remove bitstream from the state
*
* @param submissionId
* The submission id
* @param sectionId
* The section id
* @param fileUUID
* The bitstream UUID
*/
public removeUploadedFile(submissionId: string, sectionId: string, fileUUID: string) {
this.store.dispatch( this.store.dispatch(
new DeleteUploadedFileAction(submissionId, sectionId, fileId) new DeleteUploadedFileAction(submissionId, sectionId, fileUUID)
); );
} }
} }

View File

@@ -6,21 +6,45 @@ import { SubmissionService } from './submission.service';
import { SubmissionObject } from '../core/submission/models/submission-object.model'; import { SubmissionObject } from '../core/submission/models/submission-object.model';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
/**
* Instance of SubmissionService used on SSR.
*/
@Injectable() @Injectable()
export class ServerSubmissionService extends SubmissionService { export class ServerSubmissionService extends SubmissionService {
/**
* Override createSubmission parent method to return an empty observable
*
* @return Observable<SubmissionObject>
* observable of SubmissionObject
*/
createSubmission(): Observable<SubmissionObject> { createSubmission(): Observable<SubmissionObject> {
return observableOf(null); return observableOf(null);
} }
/**
* Override retrieveSubmission parent method to return an empty observable
*
* @return Observable<SubmissionObject>
* observable of SubmissionObject
*/
retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> { retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> {
return observableOf(null); return observableOf(null);
} }
/**
* Override startAutoSave parent method and return without doing anything
*
* @param submissionId
* The submission id
*/
startAutoSave(submissionId) { startAutoSave(submissionId) {
return; return;
} }
/**
* Override startAutoSave parent method and return without doing anything
*/
stopAutoSave() { stopAutoSave() {
return; return;
} }

View File

@@ -11,6 +11,9 @@ import { SubmissionService } from '../submission.service';
import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { SubmissionObject } from '../../core/submission/models/submission-object.model';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
/**
* This component allows to submit a new workspaceitem.
*/
@Component({ @Component({
selector: 'ds-submit-page', selector: 'ds-submit-page',
styleUrls: ['./submission-submit.component.scss'], styleUrls: ['./submission-submit.component.scss'],
@@ -18,14 +21,46 @@ import { Collection } from '../../core/shared/collection.model';
}) })
export class SubmissionSubmitComponent implements OnDestroy, OnInit { export class SubmissionSubmitComponent implements OnDestroy, OnInit {
/**
* The collection id this submission belonging to
* @type {string}
*/
public collectionId: string; public collectionId: string;
public model: any;
/**
* The submission self url
* @type {string}
*/
public selfUrl: string; public selfUrl: string;
/**
* The configuration object that define this submission
* @type {SubmissionDefinitionsModel}
*/
public submissionDefinition: SubmissionDefinitionsModel; public submissionDefinition: SubmissionDefinitionsModel;
/**
* The submission id
* @type {string}
*/
public submissionId: string; public submissionId: string;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} changeDetectorRef
* @param {NotificationsService} notificationsService
* @param {SubmissionService} submissioService
* @param {Router} router
* @param {TranslateService} translate
* @param {ViewContainerRef} viewContainerRef
*/
constructor(private changeDetectorRef: ChangeDetectorRef, constructor(private changeDetectorRef: ChangeDetectorRef,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private router: Router, private router: Router,
@@ -34,6 +69,9 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit {
private viewContainerRef: ViewContainerRef) { private viewContainerRef: ViewContainerRef) {
} }
/**
* Create workspaceitem on the server and initialize all instance variables
*/
ngOnInit() { ngOnInit() {
// NOTE execute the code on the browser side only, otherwise it is executed twice // NOTE execute the code on the browser side only, otherwise it is executed twice
this.subs.push( this.subs.push(
@@ -56,6 +94,9 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit {
) )
} }
/**
* Unsubscribe from all subscriptions
*/
ngOnDestroy() { ngOnDestroy() {
this.subs this.subs
.filter((subscription) => hasValue(subscription)) .filter((subscription) => hasValue(subscription))