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-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",

View File

@@ -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

View File

@@ -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<SubmissionObject> = new EventEmitter<SubmissionObject>();
/**
* A boolean representing if this dropdown button is disabled
* @type {BehaviorSubject<boolean>}
*/
public disabled$ = new BehaviorSubject<boolean>(true);
public model: any;
/**
* The search form control
* @type {FormControl}
*/
public searchField: FormControl = new FormControl();
/**
* The collection list obtained from a search
* @type {Observable<CollectionListEntry[]>}
*/
public searchListCollection$: Observable<CollectionListEntry[]>;
/**
* The selected collection id
* @type {string}
*/
public selectedCollectionId: string;
/**
* The selected collection name
* @type {Observable<string>}
*/
public selectedCollectionName$: Observable<string>;
/**
* 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();

View File

@@ -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<boolean>}
*/
public processingDepositStatus: Observable<boolean>;
/**
* A boolean representing if a submission save operation is pending
* @type {Observable<boolean>}
*/
public processingSaveStatus: Observable<boolean>;
/**
* A boolean representing if showing deposit and discard buttons
* @type {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);
/**
* 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) => {

View File

@@ -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<SectionDataObject[]>}
*/
public sectionList$: Observable<SectionDataObject[]>;
/**
* A boolean representing if there are available sections to add
* @type {Observable<boolean>}
*/
public hasSections$: Observable<boolean>;
/**
* 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);
}

View File

@@ -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<boolean>}
*/
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 = {
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<boolean> {
return this.loading;
}
/**
* Check if submission form is loading
*/
protected getSectionsList(): Observable<any> {
return this.submissionService.getSubmissionSections(this.submissionId).pipe(
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 { 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<boolean> = 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(
@@ -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

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
* The current SubmissionObjectEntry

View File

@@ -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,

View File

@@ -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);
}

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', () => {
@@ -164,7 +164,7 @@ describe('SectionFormOperationsService test suite', () => {
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 { 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<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 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;
@@ -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<string, any>
* the map of values
*/
public getValueMap(items: any[]): Map<string, any> {
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<string, any>,
pathCombiner: JsonPatchOperationPathCombiner,
event: DynamicFormControlEvent,

View File

@@ -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<boolean>
* the section status
*/
protected getSectionStatus(): Observable<boolean> {
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(

View File

@@ -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<string>;
/**
* 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<boolean>
* the section status
*/
protected getSectionStatus(): Observable<boolean> {
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))

View File

@@ -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;
}

View File

@@ -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<boolean>
* the section status
*/
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;
/**
* 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;
/**
* 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();

View File

@@ -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<boolean>;
/**
* 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<boolean>;
/**
* 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<boolean>}
* Emits true whenever section is enabled
*/
public isEnabled(): Observable<boolean> {
return this.enabled;
}
/**
* Check if section is valid
*
* @returns {Observable<boolean>}
* Emits true whenever section is valid
*/
public isValid(): Observable<boolean> {
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)
}
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);

View File

@@ -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)) {

View File

@@ -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<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[];
/**
* 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));
}
}

View File

@@ -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<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 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<boolean>}
*/
public processingDelete$ = new BehaviorSubject<boolean>(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();

View File

@@ -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);
}
}

View File

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

View File

@@ -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<SubmissionFormsModel>;
/*
/**
* 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<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,
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));
@@ -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[]]) => {
@@ -191,6 +246,14 @@ export class UploadSectionComponent extends SectionModelComponent {
);
}
/**
* 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<boolean>
* the section status
*/
protected getSectionStatus(): Observable<boolean> {
return this.bitstreamService.getUploadedFileList(this.submissionId, this.sectionData.id).pipe(
map((fileList: any[]) => (isNotUndefined(fileList) && fileList.length > 0)));

View File

@@ -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<SubmissionState>} store
*/
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> {
return this.store.select(submissionUploadedFilesFromIdSelector(submissionId, sectionId)).pipe(
map((state) => state),
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(
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<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),
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)
);
}
}

View File

@@ -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<SubmissionObject>
* observable of SubmissionObject
*/
createSubmission(): Observable<SubmissionObject> {
return observableOf(null);
}
/**
* Override retrieveSubmission parent method to return an empty observable
*
* @return Observable<SubmissionObject>
* observable of SubmissionObject
*/
retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> {
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;
}

View File

@@ -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))