From 6335d61dda2f7083829487efcedbf6ab5c258e67 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 20 Mar 2019 19:57:07 +0100 Subject: [PATCH] Added more comments --- resources/i18n/en.json | 2 +- .../edit/submission-edit.component.ts | 44 ++++- .../submission-form-collection.component.ts | 113 +++++++++++- .../submission-form-footer.component.ts | 50 +++++- .../submission-form-section-add.component.ts | 37 +++- .../form/submission-form.component.ts | 93 +++++++++- .../submission-upload-files.component.ts | 83 ++++++++- .../objects/submission-objects.effects.ts | 2 +- .../objects/submission-objects.reducer.ts | 2 +- .../container/section-container.component.ts | 52 +++++- .../section-form-operations.service.spec.ts | 4 +- .../form/section-form-operations.service.ts | 131 +++++++++++++- .../sections/form/section-form.component.ts | 24 ++- .../license/section-license.component.ts | 72 +++++++- .../sections/models/section-data.model.ts | 34 ++++ .../sections/models/section.model.ts | 63 +++++++ .../submission/sections/sections.directive.ts | 170 ++++++++++++++++-- ...tion-upload-access-conditions.component.ts | 19 ++ .../section-upload-file-edit.component.ts | 140 ++++++++++++--- .../file/section-upload-file.component.ts | 165 +++++++++++++++-- .../section-upload-file-view.component.ts | 35 +++- .../upload/section-upload.component.html | 3 - .../upload/section-upload.component.ts | 127 ++++++++++--- .../sections/upload/section-upload.service.ts | 96 ++++++++-- .../submission/server-submission.service.ts | 24 +++ .../submit/submission-submit.component.ts | 43 ++++- 26 files changed, 1505 insertions(+), 123 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 42847f8ec8..f52ca12730 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -743,7 +743,7 @@ "upload-successful": "Upload successful", "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.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": { "access-condition-label": "Access condition type", "from-label": "Access grant from", diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index d128191d79..60c8b9a7a3 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -14,17 +14,44 @@ import { SubmissionObject } from '../../core/submission/models/submission-object import { Collection } from '../../core/shared/collection.model'; import { RemoteData } from '../../core/data/remote-data'; +/** + * This component allows to edit an existing workspaceitem/workflowitem. + */ @Component({ selector: 'ds-submission-edit', styleUrls: ['./submission-edit.component.scss'], templateUrl: './submission-edit.component.html' }) - export class SubmissionEditComponent implements OnDestroy, OnInit { + + /** + * The collection id this submission belonging to + * @type {string} + */ public collectionId: string; + + /** + * The list of submission's sections + * @type {WorkspaceitemSectionsObject} + */ public sections: WorkspaceitemSectionsObject; + + /** + * The submission self url + * @type {string} + */ public selfUrl: string; + + /** + * The configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ public submissionDefinition: SubmissionDefinitionsModel; + + /** + * The submission id + * @type {string} + */ public submissionId: string; /** @@ -33,6 +60,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { */ 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, private notificationsService: NotificationsService, private route: ActivatedRoute, @@ -41,6 +78,9 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { private translate: TranslateService) { } + /** + * Retrieve workspaceitem/workflowitem from server and initialize all instance variables + */ ngOnInit() { this.subs.push(this.route.paramMap.pipe( 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() { this.subs diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index fa54280aae..2fe424bd3f 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -36,24 +36,48 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +/** + * An interface to represent a collection entry + */ interface CollectionListEntryItem { id: string; name: string; } +/** + * An interface to represent an entry in the collection list + */ interface CollectionListEntry { communities: CollectionListEntryItem[], collection: CollectionListEntryItem } +/** + * This component allows to show the current collection the submission belonging to and to change it. + */ @Component({ selector: 'ds-submission-form-collection', styleUrls: ['./submission-form-collection.component.scss'], templateUrl: './submission-form-collection.component.html' }) export class SubmissionFormCollectionComponent implements OnChanges, OnInit { + + /** + * The current collection id this submission belonging to + * @type {string} + */ @Input() currentCollectionId: string; + + /** + * The current configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ @Input() currentDefinition: string; + + /** + * The submission id + * @type {string} + */ @Input() submissionId; /** @@ -62,18 +86,69 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { */ @Output() collectionChange: EventEmitter = new EventEmitter(); + /** + * A boolean representing if this dropdown button is disabled + * @type {BehaviorSubject} + */ public disabled$ = new BehaviorSubject(true); - public model: any; + + /** + * The search form control + * @type {FormControl} + */ public searchField: FormControl = new FormControl(); + + /** + * The collection list obtained from a search + * @type {Observable} + */ public searchListCollection$: Observable; + + /** + * The selected collection id + * @type {string} + */ public selectedCollectionId: string; + + /** + * The selected collection name + * @type {Observable} + */ public selectedCollectionName$: Observable; + /** + * The JsonPatchOperationPathCombiner object + * @type {JsonPatchOperationPathCombiner} + */ protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * A boolean representing if dropdown list is scrollable to the bottom + * @type {boolean} + */ private scrollableBottom = false; + + /** + * A boolean representing if dropdown list is scrollable to the top + * @type {boolean} + */ private scrollableTop = false; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ 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, private communityDataService: CommunityDataService, private operationsBuilder: JsonPatchOperationsBuilder, @@ -81,6 +156,13 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { 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) { if (event.wheelDelta > 0 && this.scrollableTop) { 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) { this.scrollableBottom = (event.target.scrollTop + event.target.clientHeight === event.target.scrollHeight); this.scrollableTop = (event.target.scrollTop === 0); } + /** + * Initialize collection list + */ ngOnChanges(changes: SimpleChanges) { if (hasValue(changes.currentCollectionId) && hasValue(changes.currentCollectionId.currentValue)) { @@ -153,14 +243,26 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { } } + /** + * Initialize all instance variables + */ ngOnInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 'collection'); } + /** + * Unsubscribe from all subscriptions + */ ngOnDestroy(): void { 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) { this.searchField.reset(); this.disabled$.next(true); @@ -181,10 +283,19 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { ); } + /** + * Reset search form control on dropdown menu close + */ onClose() { 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) { if (!isOpen) { this.searchField.reset(); diff --git a/src/app/submission/form/footer/submission-form-footer.component.ts b/src/app/submission/form/footer/submission-form-footer.component.ts index 7245234fb4..4f4e355397 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.ts @@ -9,6 +9,9 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionScopeType } from '../../../core/submission/submission-scope-type'; import { isNotEmpty } from '../../../shared/empty.util'; +/** + * This component represents submission form footer bar. + */ @Component({ selector: 'ds-submission-form-footer', styleUrls: ['./submission-form-footer.component.scss'], @@ -16,18 +19,51 @@ import { isNotEmpty } from '../../../shared/empty.util'; }) 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} + */ public processingDepositStatus: Observable; + + /** + * A boolean representing if a submission save operation is pending + * @type {Observable} + */ public processingSaveStatus: Observable; + + /** + * A boolean representing if showing deposit and discard buttons + * @type {Observable} + */ public showDepositAndDiscard: Observable; + + /** + * A boolean representing if submission form is valid or not + * @type {Observable} + */ private submissionIsInvalid: Observable = observableOf(true); + /** + * Initialize instance variables + * + * @param {NgbModal} modalService + * @param {SubmissionRestService} restService + * @param {SubmissionService} submissionService + */ constructor(private modalService: NgbModal, private restService: SubmissionRestService, private submissionService: SubmissionService) { } + /** + * Initialize all instance variables + */ ngOnChanges(changes: SimpleChanges) { if (isNotEmpty(this.submissionId)) { this.submissionIsInvalid = this.submissionService.getSubmissionStatus(this.submissionId).pipe( @@ -40,18 +76,30 @@ export class SubmissionFormFooterComponent implements OnChanges { } } + /** + * Dispatch a submission save action + */ save(event) { this.submissionService.dispatchSave(this.submissionId); } + /** + * Dispatch a submission save for later action + */ saveLater(event) { this.submissionService.dispatchSaveForLater(this.submissionId); } + /** + * Dispatch a submission deposit action + */ public deposit(event) { this.submissionService.dispatchDeposit(this.submissionId); } + /** + * Dispatch a submission discard action + */ public confirmDiscard(content) { this.modalService.open(content).result.then( (result) => { diff --git a/src/app/submission/form/section-add/submission-form-section-add.component.ts b/src/app/submission/form/section-add/submission-form-section-add.component.ts index 20db74feac..48ba07dad1 100644 --- a/src/app/submission/form/section-add/submission-form-section-add.component.ts +++ b/src/app/submission/form/section-add/submission-form-section-add.component.ts @@ -1,30 +1,62 @@ import { Component, Input, OnInit, } from '@angular/core'; import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { SectionsService } from '../../sections/sections.service'; import { HostWindowService } from '../../../shared/host-window.service'; import { SubmissionService } from '../../submission.service'; 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({ selector: 'ds-submission-form-section-add', styleUrls: [ './submission-form-section-add.component.scss' ], templateUrl: './submission-form-section-add.component.html' }) export class SubmissionFormSectionAddComponent implements OnInit { + + /** + * The collection id this submission belonging to + * @type {string} + */ @Input() collectionId: string; + + /** + * The submission id + * @type {string} + */ @Input() submissionId: string; + /** + * The possible section list to add + * @type {Observable} + */ public sectionList$: Observable; + + /** + * A boolean representing if there are available sections to add + * @type {Observable} + */ public hasSections$: Observable; + /** + * Initialize instance variables + * + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {HostWindowService} windowService + */ constructor(private sectionService: SectionsService, private submissionService: SubmissionService, public windowService: HostWindowService) { } + /** + * Initialize all instance variables + */ ngOnInit() { this.sectionList$ = this.submissionService.getDisabledSectionsList(this.submissionId); this.hasSections$ = this.sectionList$.pipe( @@ -32,6 +64,9 @@ export class SubmissionFormSectionAddComponent implements OnInit { ) } + /** + * Dispatch an action to add a new section + */ addSection(sectionId) { this.sectionService.addSection(this.submissionId, sectionId); } diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index deb39fbae2..58725d79ff 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -15,22 +15,68 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { Collection } from '../../core/shared/collection.model'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; +/** + * This component represents the submission form. + */ @Component({ selector: 'ds-submission-submit-form', styleUrls: ['./submission-form.component.scss'], templateUrl: './submission-form.component.html', }) export class SubmissionFormComponent implements OnChanges, OnDestroy { + + /** + * The collection id this submission belonging to + * @type {string} + */ @Input() collectionId: string; + + /** + * The list of submission's sections + * @type {WorkspaceitemSectionsObject} + */ @Input() sections: WorkspaceitemSectionsObject; + + /** + * The submission self url + * @type {string} + */ @Input() selfUrl: string; + + /** + * The configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ @Input() submissionDefinition: SubmissionDefinitionsModel; + + /** + * The submission id + * @type {string} + */ @Input() submissionId: string; + /** + * The configuration id that define this submission + * @type {string} + */ public definitionId: string; - public test = true; + + /** + * A boolean representing if a submission form is pending + * @type {Observable} + */ public loading: Observable = observableOf(true); - public submissionSections: Observable; + + /** + * Observable of the list of submission's sections + * @type {Observable} + */ + public submissionSections: Observable; + + /** + * The uploader configuration options + * @type {UploaderOptions} + */ public uploadFilesOptions: UploaderOptions = { url: '', authToken: null, @@ -38,9 +84,26 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { itemAlias: null }; + /** + * A boolean representing if component is active + * @type {boolean} + */ protected isActive: boolean; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ protected subs: Subscription[] = []; + /** + * Initialize instance variables + * + * @param {AuthService} authService + * @param {ChangeDetectorRef} changeDetectorRef + * @param {HALEndpointService} halService + * @param {SubmissionService} submissionService + */ constructor( private authService: AuthService, private changeDetectorRef: ChangeDetectorRef, @@ -49,9 +112,14 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { this.isActive = true; } + /** + * Initialize all instance variables and retrieve form configuration + */ ngOnChanges(changes: SimpleChanges) { if (this.collectionId && this.submissionId) { this.isActive = true; + + // retrieve submission's section list this.submissionSections = this.submissionService.getSubmissionObject(this.submissionId).pipe( filter(() => this.isActive), 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( filter(() => this.isActive), map((submission: SubmissionObjectEntry) => submission.isLoading), map((isLoading: boolean) => isLoading), distinctUntilChanged()); + // init submission state this.subs.push( this.halService.getEndpoint('workspaceitems').pipe( filter((href: string) => isNotEmpty(href)), @@ -89,10 +159,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { this.changeDetectorRef.detectChanges(); }) ); + + // start auto save this.submissionService.startAutoSave(this.submissionId); } } + /** + * Unsubscribe from all subscriptions, destroy instance variables + * and reset submission state + */ ngOnDestroy() { this.isActive = false; this.submissionService.stopAutoSave(); @@ -102,6 +178,13 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { .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) { this.collectionId = (submissionObject.collection as Collection).id; 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 { return this.loading; } + /** + * Check if submission form is loading + */ protected getSectionsList(): Observable { return this.submissionService.getSubmissionSections(this.submissionId).pipe( filter((sections: SectionDataObject[]) => isNotEmpty(sections)), diff --git a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts index 0cc226772c..be3e6b5c8c 100644 --- a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts +++ b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts @@ -14,24 +14,72 @@ import { UploaderOptions } from '../../../shared/uploader/uploader-options.model import parseSectionErrors from '../../utils/parseSectionErrors'; 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({ selector: 'ds-submission-upload-files', templateUrl: './submission-upload-files.component.html', }) export class SubmissionUploadFilesComponent implements OnChanges { - @Input() collectionId; - @Input() submissionId; - @Input() sectionId; + /** + * The collection id this submission belonging to + * @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; + /** + * A boolean representing if is possible to active drop zone over the document page + * @type {boolean} + */ public enableDragOverDocument = true; + + /** + * i18n message label + * @type {string} + */ public dropOverDocumentMsg = 'submission.sections.upload.drop-message'; + + /** + * i18n message label + * @type {string} + */ 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 = observableOf(false); + /** + * Save submission before to upload a file + */ public onBeforeUpload = () => { const sub: Subscription = this.operationsService.jsonPatchByResourceType( this.submissionService.getSubmissionObjectLinkName(), @@ -42,6 +90,15 @@ export class SubmissionUploadFilesComponent implements OnChanges { return sub; }; + /** + * Initialize instance variables + * + * @param {NotificationsService} notificationsService + * @param {SubmissionJsonPatchOperationsService} operationsService + * @param {SectionsService} sectionService + * @param {SubmissionService} submissionService + * @param {TranslateService} translate + */ constructor(private notificationsService: NotificationsService, private operationsService: SubmissionJsonPatchOperationsService, private sectionService: SectionsService, @@ -49,10 +106,19 @@ export class SubmissionUploadFilesComponent implements OnChanges { private translate: TranslateService) { } + /** + * Check if upload functionality is enabled + */ ngOnChanges() { 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) { // Checks if upload section is enabled so do upload this.subs.push( @@ -61,8 +127,8 @@ export class SubmissionUploadFilesComponent implements OnChanges { .subscribe((isUploadEnabled) => { if (isUploadEnabled) { - const {sections} = workspaceitem; - const {errors} = workspaceitem; + const { sections } = workspaceitem; + const { errors } = workspaceitem; const errorsList = parseSectionErrors(errors); if (sections && isNotEmpty(sections)) { @@ -87,12 +153,15 @@ export class SubmissionUploadFilesComponent implements OnChanges { ); } + /** + * Show error notification on upload fails + */ public onUploadError() { 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() { this.subs diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index f4b74807cf..f5c8887320 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -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 * The current SubmissionObjectEntry diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index 68ac7d56b9..1a65783945 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -138,7 +138,7 @@ export interface SubmissionObjectEntry { collection?: string, /** - * The configuration name tha define this submission + * The configuration name that define this submission */ definition?: string, diff --git a/src/app/submission/sections/container/section-container.component.ts b/src/app/submission/sections/container/section-container.component.ts index 7339df5892..b1453dffb3 100644 --- a/src/app/submission/sections/container/section-container.component.ts +++ b/src/app/submission/sections/container/section-container.component.ts @@ -3,29 +3,64 @@ import { Component, Injector, Input, OnInit, ViewChild } from '@angular/core'; import { SectionsDirective } from '../sections.directive'; import { SectionDataObject } from '../models/section-data.model'; import { rendersSectionType } from '../sections-decorator'; -import { SectionsType } from '../sections-type'; import { AlertType } from '../../../shared/alerts/aletrs-type'; +/** + * This component represents a section that contains the submission license form. + */ @Component({ selector: 'ds-submission-form-section-container', templateUrl: './section-container.component.html', styleUrls: ['./section-container.component.scss'] }) export class SectionContainerComponent implements OnInit { + + /** + * The collection id this submission belonging to + * @type {string} + */ @Input() collectionId: string; + + /** + * The section data + * @type {SectionDataObject} + */ @Input() sectionData: SectionDataObject; + + /** + * The submission id + * @type {string} + */ @Input() submissionId: string; + /** + * The AlertType enumeration + * @type {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; + /** + * Initialize instance variables + * + * @param {Injector} injector + */ constructor(private injector: Injector) { } + /** + * Initialize all instance variables + */ ngOnInit() { this.objectInjector = Injector.create({ providers: [ @@ -37,12 +72,21 @@ export class SectionContainerComponent implements OnInit { }); } + /** + * Remove section from submission form + * + * @param event + * the event emitted + */ public removeSection(event) { event.preventDefault(); event.stopPropagation(); this.sectionRef.removeSection(this.submissionId, this.sectionData.id); } + /** + * Find the correct component based on the section's type + */ getSectionContent(): string { return rendersSectionType(this.sectionData.sectionType); } diff --git a/src/app/submission/sections/form/section-form-operations.service.spec.ts b/src/app/submission/sections/form/section-form-operations.service.spec.ts index 1519da7557..c90fc62360 100644 --- a/src/app/submission/sections/form/section-form-operations.service.spec.ts +++ b/src/app/submission/sections/form/section-form-operations.service.spec.ts @@ -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', () => { @@ -164,7 +164,7 @@ describe('SectionFormOperationsService test suite', () => { parent: null }; - expect(service.isPartOfArrayOfGroup(model)).toBeFalsy(); + expect(service.isPartOfArrayOfGroup(model as any)).toBeFalsy(); }); }); diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 9483c8d23e..2d6b1c5477 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -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 { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; +/** + * The service handling all form section operations + */ @Injectable() 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, event: DynamicFormControlEvent, 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 { let fieldIndex: number; if (isNotEmpty(event)) { @@ -60,7 +91,15 @@ export class SectionFormOperationsService { 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) && (model.parent as any).type === DYNAMIC_FORM_CONTROL_TYPE_GROUP && (model.parent as any).parent @@ -68,7 +107,15 @@ export class SectionFormOperationsService { && (model.parent as any).parent.context.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY); } - public getQualdropValueMap(event): Map { + /** + * Return a map for the values of a Qualdrop field + * + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + * @return Map + * the map of values + */ + public getQualdropValueMap(event: DynamicFormControlEvent): Map { const metadataValueMap = new Map(); const context = this.formBuilder.isQualdropGroup(event.model) @@ -87,12 +134,28 @@ export class SectionFormOperationsService { 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 { const fieldIndex = this.getArrayIndexFromEvent(event); const fieldId = this.getFieldPathSegmentedFromChangeEvent(event); 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 { const fieldIndex = this.getArrayIndexFromEvent(event); const metadataValueMap = new Map(); @@ -117,6 +180,14 @@ export class SectionFormOperationsService { 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 { let fieldId; if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) { @@ -129,6 +200,14 @@ export class SectionFormOperationsService { 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 { let fieldValue; const value = (event.model as any).value; @@ -142,12 +221,12 @@ export class SectionFormOperationsService { if ((event.model as DsDynamicInputModel).hasAuthority) { if (Array.isArray(value)) { value.forEach((authority, index) => { - authority = Object.assign(new AuthorityValue(), authority, {language}); + authority = Object.assign(new AuthorityValue(), authority, { language }); value[index] = authority; }); fieldValue = value; } else { - fieldValue = Object.assign(new AuthorityValue(), value, {language}); + fieldValue = Object.assign(new AuthorityValue(), value, { language }); } } else { // Language without Authority (input, textArea) @@ -162,6 +241,14 @@ export class SectionFormOperationsService { return fieldValue; } + /** + * Return a map for the values of an array of field + * + * @param items + * the list of items + * @return Map + * the map of values + */ public getValueMap(items: any[]): Map { const metadataValueMap = new Map(); @@ -177,6 +264,16 @@ export class SectionFormOperationsService { 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, event: DynamicFormControlEvent, 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, event: DynamicFormControlEvent, 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, pathCombiner: JsonPatchOperationPathCombiner, event: DynamicFormControlEvent, diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 6ed91782d0..048f109f54 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -42,46 +42,55 @@ export class FormSectionComponent extends SectionModelComponent { /** * The form id + * @type {string} */ - public formId; + public formId: string; /** * The form model + * @type {DynamicFormControlModel[]} */ public formModel: DynamicFormControlModel[]; /** * A boolean representing if this section is updating + * @type {boolean} */ public isUpdating = false; /** * A boolean representing if this section is loading + * @type {boolean} */ public isLoading = true; /** * The form config + * @type {SubmissionFormsModel} */ protected formConfig: SubmissionFormsModel; /** * The form data + * @type {any} */ protected formData: any = Object.create({}); /** * The [JsonPatchOperationPathCombiner] object + * @type {JsonPatchOperationPathCombiner} */ protected pathCombiner: JsonPatchOperationPathCombiner; /** * The [FormFieldPreviousValueObject] object + * @type {FormFieldPreviousValueObject} */ protected previousValue: FormFieldPreviousValueObject = new FormFieldPreviousValueObject(); /** * The list of Subscription + * @type {Array} */ protected subs: Subscription[] = []; @@ -92,6 +101,7 @@ export class FormSectionComponent extends SectionModelComponent { /** * Initialize instance variables + * * @param {ChangeDetectorRef} cdr * @param {FormBuilderService} formBuilderService * @param {SectionFormOperationsService} formOperationsService @@ -158,6 +168,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Get section status + * + * @return Observable + * the section status */ protected getSectionStatus(): Observable { return this.formService.isValid(this.formId); @@ -290,6 +303,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Method called when a form dfChange event is fired. * Dispatch form operations based on changes. + * + * @param event + * the [[DynamicFormControlEvent]] emitted */ onChange(event: DynamicFormControlEvent): void { this.formOperationsService.dispatchOperationsFromEvent( @@ -308,6 +324,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Method called when a form dfFocus event is fired. * Initialize [FormFieldPreviousValueObject] instance. + * + * @param event + * the [[DynamicFormControlEvent]] emitted */ onFocus(event: DynamicFormControlEvent): void { const value = this.formOperationsService.getFieldValueFromChangeEvent(event); @@ -324,6 +343,9 @@ export class FormSectionComponent extends SectionModelComponent { /** * Method called when a form remove event is fired. * Dispatch form operations based on changes. + * + * @param event + * the [[DynamicFormControlEvent]] emitted */ onRemove(event: DynamicFormControlEvent): void { this.formOperationsService.dispatchOperationsFromEvent( diff --git a/src/app/submission/sections/license/section-license.component.ts b/src/app/submission/sections/license/section-license.component.ts index 86051ae15b..f4dae0565b 100644 --- a/src/app/submission/sections/license/section-license.component.ts +++ b/src/app/submission/sections/license/section-license.component.ts @@ -29,6 +29,9 @@ import { SectionsService } from '../sections.service'; import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { FormComponent } from '../../../shared/form/form.component'; +/** + * This component represents a section that contains the submission license form. + */ @Component({ selector: 'ds-submission-section-license', styleUrls: ['./section-license.component.scss'], @@ -37,17 +40,68 @@ import { FormComponent } from '../../../shared/form/form.component'; @renderSectionFor(SectionsType.License) export class LicenseSectionComponent extends SectionModelComponent { - public formId; + /** + * The form id + * @type {string} + */ + public formId: string; + + /** + * The form model + * @type {DynamicFormControlModel[]} + */ public formModel: DynamicFormControlModel[]; + + /** + * The [[DynamicFormLayout]] object + * @type {DynamicFormLayout} + */ public formLayout: DynamicFormLayout = SECTION_LICENSE_FORM_LAYOUT; + + /** + * A boolean representing if to show form submit and cancel buttons + * @type {boolean} + */ public displaySubmit = false; + + /** + * The submission license text + * @type {Array} + */ public licenseText$: Observable; + /** + * The [[JsonPatchOperationPathCombiner]] object + * @type {JsonPatchOperationPathCombiner} + */ protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ protected subs: Subscription[] = []; + /** + * The FormComponent reference + */ @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, protected collectionDataService: CollectionDataService, protected formBuilderService: FormBuilderService, @@ -62,6 +116,9 @@ export class LicenseSectionComponent extends SectionModelComponent { super(injectedCollectionId, injectedSectionData, injectedSubmissionId); } + /** + * Initialize all instance variables and retrieve submission license + */ onSectionInit() { this.pathCombiner = new JsonPatchOperationPathCombiner('sections', 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 + * the section status + */ protected getSectionStatus(): Observable { const model = this.formBuilderService.findById('granted', this.formModel); return (model as DynamicCheckboxModel).valueUpdates.pipe( @@ -133,6 +196,10 @@ export class LicenseSectionComponent extends SectionModelComponent { startWith((model as DynamicCheckboxModel).value)); } + /** + * Method called when a form dfChange event is fired. + * Dispatch form operations based on changes. + */ onChange(event: DynamicFormControlEvent) { const path = this.formOperationsService.getFieldPathSegmentedFromChangeEvent(event); const value = this.formOperationsService.getFieldValueFromChangeEvent(event); @@ -145,6 +212,9 @@ export class LicenseSectionComponent extends SectionModelComponent { } } + /** + * Unsubscribe from all subscriptions + */ onSectionDestroy() { this.subs .filter((subscription) => hasValue(subscription)) diff --git a/src/app/submission/sections/models/section-data.model.ts b/src/app/submission/sections/models/section-data.model.ts index 230b36eb94..8feb78fa69 100644 --- a/src/app/submission/sections/models/section-data.model.ts +++ b/src/app/submission/sections/models/section-data.model.ts @@ -2,14 +2,48 @@ import { SubmissionSectionError } from '../../objects/submission-objects.reducer import { WorkspaceitemSectionDataType } from '../../../core/submission/models/workspaceitem-sections.model'; import { SectionsType } from '../sections-type'; +/** + * An interface to represent section model + */ export interface SectionDataObject { + + /** + * The section configuration url + */ config: string; + + /** + * The section data object + */ data: WorkspaceitemSectionDataType; + + /** + * The list of the section errors + */ errors: SubmissionSectionError[]; + + /** + * The section header + */ header: string; + + /** + * The section id + */ id: string; + + /** + * A boolean representing if this section is mandatory + */ mandatory: boolean; + + /** + * The section type + */ sectionType: SectionsType; + /** + * Eventually additional fields + */ [propName: string]: any; } diff --git a/src/app/submission/sections/models/section.model.ts b/src/app/submission/sections/models/section.model.ts index 016ce62067..4e9821dcd1 100644 --- a/src/app/submission/sections/models/section.model.ts +++ b/src/app/submission/sections/models/section.model.ts @@ -16,12 +16,44 @@ export interface SectionDataModel { */ export abstract class SectionModelComponent implements OnDestroy, OnInit, SectionDataModel { protected abstract sectionService: SectionsService; + + /** + * The collection id this submission belonging to + * @type {string} + */ collectionId: string; + + /** + * The section data + * @type {SectionDataObject} + */ sectionData: SectionDataObject; + + /** + * The submission id + * @type {string} + */ submissionId: string; + + /** + * A boolean representing if this section is valid + * @type {boolean} + */ protected valid: boolean; + + /** + * The Subscription to section status observable + * @type {Subscription} + */ private sectionStatusSub: Subscription; + /** + * Initialize instance variables + * + * @param {string} injectedCollectionId + * @param {SectionDataObject} injectedSectionData + * @param {string} injectedSubmissionId + */ public constructor(@Inject('collectionIdProvider') public injectedCollectionId: string, @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, @Inject('submissionIdProvider') public injectedSubmissionId: string) { @@ -30,15 +62,43 @@ export abstract class SectionModelComponent implements OnDestroy, OnInit, Sectio this.submissionId = injectedSubmissionId; } + /** + * Call abstract methods on component init + */ ngOnInit(): void { this.onSectionInit(); this.updateSectionStatus(); } + /** + * Abstract method to implement to get section status + * + * @return Observable + * the section status + */ protected abstract getSectionStatus(): Observable; + + /** + * Abstract method called on component init. + * It must be used instead of ngOnInit on the component that extend this abstract class + * + * @return Observable + * the section status + */ 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 + * the section status + */ protected abstract onSectionDestroy(): void; + /** + * Subscribe to section status + */ protected updateSectionStatus(): void { this.sectionStatusSub = this.getSectionStatus().pipe( 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 { if (hasValue(this.sectionStatusSub)) { this.sectionStatusSub.unsubscribe(); diff --git a/src/app/submission/sections/sections.directive.ts b/src/app/submission/sections/sections.directive.ts index 54d28b4d5f..0efb7225aa 100644 --- a/src/app/submission/sections/sections.directive.ts +++ b/src/app/submission/sections/sections.directive.ts @@ -10,28 +10,90 @@ import { SubmissionSectionError, SubmissionSectionObject } from '../objects/subm import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionErrorPaths'; import { SubmissionService } from '../submission.service'; +/** + * Directive for handling generic section functionality + */ @Directive({ selector: '[dsSection]', exportAs: 'sectionRef' }) export class SectionsDirective implements OnDestroy, OnInit { + + /** + * A boolean representing if section is mandatory + * @type {boolean} + */ @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[] = []; + + /** + * The list of all errors related to the element belonging to this section + * @type {Array} + */ public allSectionErrors: string[] = []; + + /** + * A boolean representing if section is active + * @type {boolean} + */ private active = true; - private animation = !this.mandatory; + + /** + * A boolean representing if section is enabled + * @type {boolean} + */ private enabled: Observable; + + /** + * A boolean representing the panel collapsible state: opened (true) or closed (false) + * @type {boolean} + */ private sectionState = this.mandatory; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ private subs: Subscription[] = []; + + /** + * A boolean representing if section is valid + * @type {boolean} + */ private valid: Observable; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} changeDetectorRef + * @param {SubmissionService} submissionService + * @param {SectionsService} sectionService + */ constructor(private changeDetectorRef: ChangeDetectorRef, private submissionService: SubmissionService, private sectionService: SectionsService) { } + /** + * Initialize instance variables + */ ngOnInit() { this.valid = this.sectionService.isSectionValid(this.submissionId, this.sectionId).pipe( map((valid: boolean) => { @@ -78,67 +140,145 @@ export class SectionsDirective implements OnDestroy, OnInit { this.enabled = this.sectionService.isSectionEnabled(this.submissionId, this.sectionId); } + /** + * Unsubscribe from all subscriptions + */ ngOnDestroy() { this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); } + /** + * Change section state + * + * @param event + * the event emitted + */ public sectionChange(event) { 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; } - public isMandatory() { + /** + * Check if section is mandatory + * + * @returns {boolean} + * Returns true when section is mandatory + */ + public isMandatory(): boolean { 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 { return this.active; } + /** + * Check if section is enabled + * + * @returns {Observable} + * Emits true whenever section is enabled + */ public isEnabled(): Observable { return this.enabled; } + /** + * Check if section is valid + * + * @returns {Observable} + * Emits true whenever section is valid + */ public isValid(): Observable { 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} + * Emits true whenever section is valid + */ + public removeSection(submissionId: string, sectionId: string) { 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 } - 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) || (this.allSectionErrors && this.allSectionErrors.length > 0) } - public getErrors() { + /** + * Return section errors + * + * @returns {Array} + * Returns section errors list + */ + public getErrors(): string[] { 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) { 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); } + /** + * Remove all errors from list + */ public resetErrors() { if (isNotEmpty(this.genericSectionErrors)) { this.sectionService.dispatchRemoveSectionErrors(this.submissionId, this.sectionId); diff --git a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts index a5252a46f3..f13f730452 100644 --- a/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts +++ b/src/app/submission/sections/upload/accessConditions/section-upload-access-conditions.component.ts @@ -8,18 +8,37 @@ import { isEmpty } from '../../../../shared/empty.util'; import { Group } from '../../../../core/eperson/models/group.model'; import { RemoteData } from '../../../../core/data/remote-data'; +/** + * This component represents a badge that describe an access condition + */ @Component({ selector: 'ds-section-upload-access-conditions', templateUrl: './section-upload-access-conditions.component.html', }) export class SectionUploadAccessConditionsComponent implements OnInit { + /** + * The list of resource policy + * @type {Array} + */ @Input() accessConditions: ResourcePolicy[]; + /** + * The list of access conditions + * @type {Array} + */ public accessConditionsList = []; + /** + * Initialize instance variables + * + * @param {GroupEpersonService} groupService + */ constructor(private groupService: GroupEpersonService) {} + /** + * Retrieve access conditions list + */ ngOnInit() { this.accessConditions.forEach((accessCondition: ResourcePolicy) => { if (isEmpty(accessCondition.name)) { diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index ff441d6102..9f1b49c221 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -1,6 +1,6 @@ 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 { DYNAMIC_FORM_CONTROL_TYPE_DATEPICKER, DynamicDateControlModel, @@ -12,6 +12,8 @@ import { DynamicFormGroupModel, DynamicSelectModel } 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 { BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG, @@ -35,37 +37,113 @@ import { AccessConditionOption } from '../../../../../core/config/models/config- import { SubmissionService } from '../../../../submission.service'; import { FormService } from '../../../../../shared/form/form.service'; import { FormComponent } from '../../../../../shared/form/form.component'; -import { FormControl } from '@angular/forms'; import { Group } from '../../../../../core/eperson/models/group.model'; +/** + * This component represents the edit form for bitstream + */ @Component({ selector: 'ds-submission-upload-section-file-edit', templateUrl: './section-upload-file-edit.component.html', }) export class UploadSectionFileEditComponent implements OnChanges { + /** + * The list of available access condition + * @type {Array} + */ @Input() availableAccessConditionOptions: any[]; - @Input() availableAccessConditionGroups: Map; - @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; + + /** + * 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[]; + /** + * The FormComponent reference + */ @ViewChild('formRef') public formRef: FormComponent; + /** + * Initialize instance variables + * + * @param {ChangeDetectorRef} cdr + * @param {FormBuilderService} formBuilderService + * @param {FormService} formService + * @param {SubmissionService} submissionService + */ constructor(private cdr: ChangeDetectorRef, private formBuilderService: FormBuilderService, private formService: FormService, private submissionService: SubmissionService) { } + /** + * Dispatch form model init + */ ngOnChanges() { if (this.fileData && this.formId) { this.formModel = this.buildFileEditForm(); @@ -73,8 +151,10 @@ export class UploadSectionFileEditComponent implements OnChanges { } } + /** + * Initialize form model + */ 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]); configDescr.repeatable = false; const configForm = Object.assign({}, this.configMetadataForm, { @@ -107,7 +187,7 @@ export class UploadSectionFileEditComponent implements OnChanges { } 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 hasEnd = []; const hasGroups = []; @@ -153,6 +233,12 @@ export class UploadSectionFileEditComponent implements OnChanges { return formModel; } + /** + * Initialize form model values + * + * @param formModel + * The form model + */ public initModelData(formModel: DynamicFormControlModel[]) { this.fileData.accessConditions.forEach((accessCondition, index) => { 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) { if (event.model.id === 'name') { 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; this.availableAccessConditionOptions.filter((element) => element.name === control.value) .forEach((element) => accessCondition = element); if (isNotEmpty(accessCondition)) { const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true; - const groupControl: FormControl = control.parent.get('groupUUID'); - const startDateControl: FormControl = control.parent.get('startDate'); - const endDateControl: FormControl = control.parent.get('endDate'); + const groupControl: FormControl = control.parent.get('groupUUID') as FormControl; + const startDateControl: FormControl = control.parent.get('startDate') as FormControl; + const endDateControl: FormControl = control.parent.get('endDate') as FormControl; // Clear previous state groupControl.markAsUntouched(); @@ -236,8 +336,8 @@ export class UploadSectionFileEditComponent implements OnChanges { const confGroup = { relation: groupModel.relation }; const groupsConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_CONFIG, confGroup); groupsConfig.options = groupOptions; - model.parent.group.pop(); - model.parent.group.push(new DynamicSelectModel(groupsConfig, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT)); + (model.parent as DynamicFormGroupModel).group.pop(); + (model.parent as DynamicFormGroupModel).group.push(new DynamicSelectModel(groupsConfig, BITSTREAM_FORM_ACCESS_CONDITION_GROUPS_LAYOUT)); } } diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts index 45d91428fe..4d8f8119d7 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts @@ -1,6 +1,6 @@ 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 { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; 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 { Group } from '../../../../core/eperson/models/group.model'; +/** + * This component represents a single bitstream contained in the submission + */ @Component({ selector: 'ds-submission-upload-section-file', styleUrls: ['./section-upload-file.component.scss'], @@ -30,28 +33,129 @@ import { Group } from '../../../../core/eperson/models/group.model'; }) export class UploadSectionFileComponent implements OnChanges, OnInit { + /** + * The list of available access condition + * @type {Array} + */ @Input() availableAccessConditionOptions: any[]; - @Input() availableAccessConditionGroups: Map; - @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; + + /** + * 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 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[]; + + /** + * A boolean representing if a submission delete operation is pending + * @type {BehaviorSubject} + */ public processingDelete$ = new BehaviorSubject(false); + /** + * The [JsonPatchOperationPathCombiner] object + * @type {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; + /** + * 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, private fileService: FileService, private formService: FormService, @@ -64,6 +168,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { this.readMode = true; } + /** + * Retrieve bitstream's metadata + */ ngOnChanges() { if (this.availableAccessConditionOptions && this.availableAccessConditionGroups) { // Retrieve file state @@ -79,11 +186,17 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { } } + /** + * Initialize instance variables + */ ngOnInit() { this.formId = this.formService.getUniqueId(this.fileId); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex); } + /** + * Delete bitstream from submission + */ protected deleteFile() { this.operationsBuilder.remove(this.pathCombiner.getPath()); this.subscriptions.push(this.operationsService.jsonPatchByResourceID( @@ -97,6 +210,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { })); } + /** + * Show confirmation dialog for delete + */ public confirmDelete(content) { this.modalService.open(content).result.then( (result) => { @@ -108,6 +224,9 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { ); } + /** + * Perform bitstream download + */ public downloadBitstreamFile() { this.halService.getEndpoint('bitstreams').pipe( first()) @@ -117,9 +236,16 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { }); } + /** + * Save bitstream metadata + * + * @param event + * the click event emitted + */ public saveBitstreamData(event) { event.preventDefault(); + // validate form this.formService.validateAllFormFields(this.fileEditComp.formRef.formGroup); this.subscriptions.push(this.formService.isValid(this.formId).pipe( take(1), @@ -127,6 +253,7 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { flatMap(() => this.formService.getFormData(this.formId)), take(1), flatMap((formData: any) => { + // collect bitstream metadata Object.keys((formData.metadata)) .filter((key) => isNotEmpty(formData.metadata[key])) .forEach((key) => { @@ -174,6 +301,7 @@ export class UploadSectionFileComponent implements OnChanges, OnInit { this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true); } + // dispatch a PATCH request to save metadata return this.operationsService.jsonPatchByResourceID( this.submissionService.getSubmissionObjectLinkName(), 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; return (temp) ? temp.value : undefined; } + /** + * Switch from edit form to metadata view + */ public switchMode() { this.readMode = !this.readMode; this.cdr.detectChanges(); diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts index a61979e74f..cafd3c7947 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts @@ -5,17 +5,42 @@ import { isNotEmpty } from '../../../../../shared/empty.util'; import { Metadata } from '../../../../../core/shared/metadata.utils'; import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata.models'; +/** + * This component allow to show bitstream's metadata + */ @Component({ selector: 'ds-submission-upload-section-file-view', templateUrl: './section-upload-file-view.component.html', }) export class UploadSectionFileViewComponent implements OnInit { + + /** + * The bitstream's metadata data + * @type {WorkspaceitemSectionUploadFileObject} + */ @Input() fileData: WorkspaceitemSectionUploadFileObject; + /** + * The [[MetadataMap]] object + * @type {MetadataMap} + */ public metadata: MetadataMap = Object.create({}); + + /** + * The bitstream's title key + * @type {string} + */ public fileTitleKey = 'Title'; + + /** + * The bitstream's description key + * @type {string} + */ public fileDescrKey = 'Description'; + /** + * Initialize instance variables + */ ngOnInit() { if (isNotEmpty(this.fileData.metadata)) { 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); } } diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html index 080f7a1f04..afe8f3314a 100644 --- a/src/app/submission/sections/upload/section-upload.component.html +++ b/src/app/submission/sections/upload/section-upload.component.html @@ -15,13 +15,10 @@
- - {{ 'submission.sections.upload.header.policy.default.nolist' | translate:{ "collectionName": collectionName } }} - {{ 'submission.sections.upload.header.policy.default.withlist' | translate:{ "collectionName": collectionName } }} diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 0c2eda5d77..3145cb8301 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -1,6 +1,6 @@ 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 { SectionModelComponent } from '../models/section.model'; @@ -33,6 +33,9 @@ export interface AccessConditionGroupsMapEntry { groups: Group[] } +/** + * This component represents a section that contains submission's bitstreams + */ @Component({ selector: 'ds-submission-section-upload', styleUrls: ['./section-upload.component.scss'], @@ -41,37 +44,84 @@ export interface AccessConditionGroupsMapEntry { @renderSectionFor(SectionsType.Upload) export class UploadSectionComponent extends SectionModelComponent { + /** + * The AlertType enumeration + * @type {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; - /* + /** * Default access conditions of this collection + * @type {Array} */ 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; - /* + /** * List of available access conditions that could be setted to files */ public availableAccessConditionOptions: AccessConditionOption[]; // List of accessConditions that an user can select - /* + /** * List of Groups available for every access condition */ protected availableGroups: Map; // 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, private changeDetectorRef: ChangeDetectorRef, private collectionDataService: CollectionDataService, @@ -84,10 +134,14 @@ export class UploadSectionComponent extends SectionModelComponent { super(undefined, injectedSectionData, injectedSubmissionId); } + /** + * Initialize all instance variables and retrieve collection default access conditions + */ onSectionInit() { const config$ = this.uploadsConfigService.getConfigByHref(this.sectionData.config).pipe( map((config) => config.payload)); + // retrieve configuration for the bitstream's metadata form this.configMetadataForm$ = config$.pipe( take(1), map((config: SubmissionUploadsModel) => config.metadata)); @@ -124,7 +178,7 @@ export class UploadSectionComponent extends SectionModelComponent { this.availableGroups = new Map(); const mapGroups$: Array> = []; - // Retrieve Groups for accessConditionPolicies + // Retrieve Groups for accessCondition Policies this.availableAccessConditionOptions.forEach((accessCondition: AccessConditionOption) => { if (accessCondition.hasEndDate === true || accessCondition.hasStartDate === true) { if (accessCondition.groupUUID) { @@ -148,7 +202,7 @@ export class UploadSectionComponent extends SectionModelComponent { accessCondition: accessCondition.name, groups: rd.payload.page } as AccessConditionGroupsMapEntry)) - )); + )); } } }); @@ -164,8 +218,9 @@ export class UploadSectionComponent extends SectionModelComponent { this.availableGroups.set(entry.accessCondition, entry.groups); }); this.changeDetectorRef.detectChanges(); - }) - , + }), + + // retrieve submission's bitstreams from state combineLatest(this.configMetadataForm$, this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id)).pipe( filter(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => { @@ -173,24 +228,32 @@ export class UploadSectionComponent extends SectionModelComponent { }), distinctUntilChanged()) .subscribe(([configMetadataForm, fileList]: [SubmissionFormsModel, any[]]) => { - this.fileList = []; - this.fileIndexes = []; - this.fileNames = []; - this.changeDetectorRef.detectChanges(); - if (isNotUndefined(fileList) && fileList.length > 0) { - fileList.forEach((file) => { - this.fileList.push(file); - this.fileIndexes.push(file.uuid); - this.fileNames.push(this.getFileName(configMetadataForm, file)); - }); - } + this.fileList = []; + this.fileIndexes = []; + this.fileNames = []; + this.changeDetectorRef.detectChanges(); + if (isNotUndefined(fileList) && fileList.length > 0) { + fileList.forEach((file) => { + this.fileList.push(file); + this.fileIndexes.push(file.uuid); + 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 { const metadataName: string = configMetadataForm.rows[0].fields[0].selectableMetadata[0].metadata; let title: string; @@ -203,6 +266,12 @@ export class UploadSectionComponent extends SectionModelComponent { return title; } + /** + * Get section status + * + * @return Observable + * the section status + */ protected getSectionStatus(): Observable { return this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id).pipe( map((fileList: any[]) => (isNotUndefined(fileList) && fileList.length > 0))); diff --git a/src/app/submission/sections/upload/section-upload.service.ts b/src/app/submission/sections/upload/section-upload.service.ts index ab18807fa5..a851fa9daf 100644 --- a/src/app/submission/sections/upload/section-upload.service.ts +++ b/src/app/submission/sections/upload/section-upload.service.ts @@ -14,51 +14,127 @@ import { submissionUploadedFileFromUuidSelector, submissionUploadedFilesFromIdSe import { isUndefined } from '../../../shared/empty.util'; import { WorkspaceitemSectionUploadFileObject } from '../../../core/submission/models/workspaceitem-section-upload-file.model'; +/** + * A service that provides methods to handle submission's bitstream state. + */ @Injectable() export class SectionUploadService { + /** + * Initialize service variables + * + * @param {Store} store + */ constructor(private store: Store) {} + /** + * 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 { return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe( map((state) => state), distinctUntilChanged()); } - public getFileData(submissionId: string, sectionId: string, fileUuid: string): Observable { + /** + * 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 { return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe( filter((state) => !isUndefined(state)), map((state) => { let fileState; Object.keys(state) - .filter((key) => state[key].uuid === fileUuid) + .filter((key) => state[key].uuid === fileUUID) .forEach((key) => fileState = state[key]); return fileState; }), distinctUntilChanged()); } - public getDefaultPolicies(submissionId: string, sectionId: string, fileId: string): Observable { - 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 { + return this.store.select(submissionUploadedFileFromUuidSelector(submissionId, sectionId, fileUUID)).pipe( map((state) => state), 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( - 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( - 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( - new DeleteUploadedFileAction(submissionId, sectionId, fileId) + new DeleteUploadedFileAction(submissionId, sectionId, fileUUID) ); } } diff --git a/src/app/submission/server-submission.service.ts b/src/app/submission/server-submission.service.ts index f9382af8d0..3aa55a9d58 100644 --- a/src/app/submission/server-submission.service.ts +++ b/src/app/submission/server-submission.service.ts @@ -6,21 +6,45 @@ import { SubmissionService } from './submission.service'; import { SubmissionObject } from '../core/submission/models/submission-object.model'; import { RemoteData } from '../core/data/remote-data'; +/** + * Instance of SubmissionService used on SSR. + */ @Injectable() export class ServerSubmissionService extends SubmissionService { + /** + * Override createSubmission parent method to return an empty observable + * + * @return Observable + * observable of SubmissionObject + */ createSubmission(): Observable { return observableOf(null); } + /** + * Override retrieveSubmission parent method to return an empty observable + * + * @return Observable + * observable of SubmissionObject + */ retrieveSubmission(submissionId): Observable> { return observableOf(null); } + /** + * Override startAutoSave parent method and return without doing anything + * + * @param submissionId + * The submission id + */ startAutoSave(submissionId) { return; } + /** + * Override startAutoSave parent method and return without doing anything + */ stopAutoSave() { return; } diff --git a/src/app/submission/submit/submission-submit.component.ts b/src/app/submission/submit/submission-submit.component.ts index 2354c2b8ab..c773093336 100644 --- a/src/app/submission/submit/submission-submit.component.ts +++ b/src/app/submission/submit/submission-submit.component.ts @@ -11,6 +11,9 @@ import { SubmissionService } from '../submission.service'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { Collection } from '../../core/shared/collection.model'; +/** + * This component allows to submit a new workspaceitem. + */ @Component({ selector: 'ds-submit-page', styleUrls: ['./submission-submit.component.scss'], @@ -18,14 +21,46 @@ import { Collection } from '../../core/shared/collection.model'; }) export class SubmissionSubmitComponent implements OnDestroy, OnInit { + /** + * The collection id this submission belonging to + * @type {string} + */ public collectionId: string; - public model: any; + + /** + * The submission self url + * @type {string} + */ public selfUrl: string; + + /** + * The configuration object that define this submission + * @type {SubmissionDefinitionsModel} + */ public submissionDefinition: SubmissionDefinitionsModel; + + /** + * The submission id + * @type {string} + */ public submissionId: string; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ 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, private notificationsService: NotificationsService, private router: Router, @@ -34,6 +69,9 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { private viewContainerRef: ViewContainerRef) { } + /** + * Create workspaceitem on the server and initialize all instance variables + */ ngOnInit() { // NOTE execute the code on the browser side only, otherwise it is executed twice this.subs.push( @@ -56,6 +94,9 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { ) } + /** + * Unsubscribe from all subscriptions + */ ngOnDestroy() { this.subs .filter((subscription) => hasValue(subscription))