From df957fc31b04be2b8c8590100382c9e1e2cf1dc6 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 19 Nov 2021 16:22:52 +0100 Subject: [PATCH 01/59] [CST-4878] Remove start and end date when embargo policy is changed to 'open access' --- .../file/section-upload-file.component.ts | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) 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 ac6c0d70c4..64d229e3a7 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 @@ -262,17 +262,24 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { .forEach((element) => accessConditionOpt = element); if (accessConditionOpt) { - accessConditionOpt = Object.assign({}, accessCondition); - accessConditionOpt.name = this.retrieveValueFromField(accessCondition.name); - if (accessCondition.startDate) { - const startDate = this.retrieveValueFromField(accessCondition.startDate); - accessConditionOpt.startDate = dateToISOFormat(startDate); - } - if (accessCondition.endDate) { - const endDate = this.retrieveValueFromField(accessCondition.endDate); - accessConditionOpt.endDate = dateToISOFormat(endDate); - } - accessConditionsToSave.push(accessConditionOpt); + const currentAccessCondition = Object.assign({}, accessCondition); + currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name); + + /* When start and end date fields are deactivated, their values may be still present in formData, + therefore it is necessary to delete them if they're not allowed by the current access condition option. */ + if (!accessConditionOpt.hasStartDate) { + delete currentAccessCondition.startDate; + } else if (accessCondition.startDate) { + const startDate = this.retrieveValueFromField(accessCondition.startDate); + currentAccessCondition.startDate = dateToISOFormat(startDate); + } + if (!accessConditionOpt.hasEndDate) { + delete currentAccessCondition.endDate; + } else if (accessCondition.endDate) { + const endDate = this.retrieveValueFromField(accessCondition.endDate); + currentAccessCondition.endDate = dateToISOFormat(endDate); + } + accessConditionsToSave.push(currentAccessCondition); } }); From 9363b0fb35f3b756f8ad6603e89c178108bf1d7f Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 19 Nov 2021 19:58:07 +0100 Subject: [PATCH 02/59] [CST-4947] File description on bitstream can now be deleted (test TBD) --- .../file/section-upload-file.component.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) 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 64d229e3a7..9807aecda3 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 @@ -6,7 +6,7 @@ import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { SectionUploadService } from '../section-upload.service'; -import { isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util'; +import { hasNoValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util'; import { FormService } from '../../../../shared/form/form.service'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; @@ -129,6 +129,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { */ protected subscriptions: Subscription[] = []; + /** + * Array containing all the form metadata defined in configMetadataForm + * @type {Array} + */ + protected formMetadata: string[] = []; + /** * The [[SubmissionSectionUploadFileEditComponent]] reference * @type {SubmissionSectionUploadFileEditComponent} @@ -158,6 +164,17 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { this.readMode = true; } + protected loadFormMetadata() { + this.configMetadataForm.rows.forEach((row) => { + row.fields.forEach((field) => { + field.selectableMetadata.forEach((metadatum) => { + this.formMetadata.push(metadatum.metadata); + }); + }); + } + ); + } + /** * Retrieve bitstream's metadata */ @@ -182,6 +199,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { ngOnInit() { this.formId = this.formService.getUniqueId(this.fileId); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex); + this.loadFormMetadata(); } /** @@ -250,6 +268,15 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { const path = `metadata/${metadataKey}`; this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true); }); + Object.keys((this.fileData.metadata)) + .filter((key) => isNotEmpty(this.fileData.metadata[key])) + .filter((key) => hasNoValue(formData.metadata[key])) + .filter((key) => this.formMetadata.includes(key)) + .forEach((key) => { + const metadataKey = key.replace(/_/g, '.'); + const path = `metadata/${metadataKey}`; + this.operationsBuilder.remove(this.pathCombiner.getPath(path)); + }); const accessConditionsToSave = []; formData.accessConditions .map((accessConditions) => accessConditions.accessConditionGroup) From 25795772257a2ec05e39da2d7f7199481f08e138 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 19 Nov 2021 19:58:07 +0100 Subject: [PATCH 03/59] [CST-4947] Test WIP --- .../section-upload-file.component.spec.ts | 25 ++++++++++++---- .../file/section-upload-file.component.ts | 29 ++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index 39aebf7413..d16f648e68 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from import { BrowserModule, By } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; -import { of as observableOf } from 'rxjs'; +import { of, of as observableOf } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { FormService } from '../../../../shared/form/form.service'; @@ -36,8 +36,20 @@ import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/mo import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { dateToISOFormat } from '../../../../shared/date.util'; +import { SubmissionFormModel } from '../../../../core/config/models/config-submission-form.model'; -describe('SubmissionSectionUploadFileComponent test suite', () => { +const configMetadataFormMock = { + rows: [{ + fields: [{ + selectableMetadata: [ + {metadata: 'dc.title', label: null, closed: false}, + {metadata: 'dc.description', label: null, closed: false} + ] + }] + }] +}; + +fdescribe('SubmissionSectionUploadFileComponent test suite', () => { let comp: SubmissionSectionUploadFileComponent; let compAsAny: any; @@ -117,6 +129,9 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { testFixture = createTestComponent(html, TestComponent) as ComponentFixture; testComp = testFixture.componentInstance; + + // testComp.configMetadataForm = configMetadataFormMock; + // testFixture.detectChanges(); }); afterEach(() => { @@ -124,9 +139,8 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { }); it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => { - + app.configMetadataForm = Object.assign(new SubmissionFormModel(), configMetadataFormMock); expect(app).toBeDefined(); - })); }); @@ -135,6 +149,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { fixture = TestBed.createComponent(SubmissionSectionUploadFileComponent); comp = fixture.componentInstance; compAsAny = comp; + compAsAny.configMetadataForm = configMetadataFormMock; submissionServiceStub = TestBed.inject(SubmissionService as any); uploadService = TestBed.inject(SectionUploadService); formService = TestBed.inject(FormService); @@ -314,7 +329,7 @@ class TestComponent { availableAccessConditionOptions; collectionId = mockSubmissionCollectionId; collectionPolicyType; - configMetadataForm$; + configMetadataForm$ = of(configMetadataFormMock); fileIndexes = []; fileList = []; fileNames = []; 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 64d229e3a7..9807aecda3 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 @@ -6,7 +6,7 @@ import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { SectionUploadService } from '../section-upload.service'; -import { isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util'; +import { hasNoValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util'; import { FormService } from '../../../../shared/form/form.service'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; @@ -129,6 +129,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { */ protected subscriptions: Subscription[] = []; + /** + * Array containing all the form metadata defined in configMetadataForm + * @type {Array} + */ + protected formMetadata: string[] = []; + /** * The [[SubmissionSectionUploadFileEditComponent]] reference * @type {SubmissionSectionUploadFileEditComponent} @@ -158,6 +164,17 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { this.readMode = true; } + protected loadFormMetadata() { + this.configMetadataForm.rows.forEach((row) => { + row.fields.forEach((field) => { + field.selectableMetadata.forEach((metadatum) => { + this.formMetadata.push(metadatum.metadata); + }); + }); + } + ); + } + /** * Retrieve bitstream's metadata */ @@ -182,6 +199,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { ngOnInit() { this.formId = this.formService.getUniqueId(this.fileId); this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionId, 'files', this.fileIndex); + this.loadFormMetadata(); } /** @@ -250,6 +268,15 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { const path = `metadata/${metadataKey}`; this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true); }); + Object.keys((this.fileData.metadata)) + .filter((key) => isNotEmpty(this.fileData.metadata[key])) + .filter((key) => hasNoValue(formData.metadata[key])) + .filter((key) => this.formMetadata.includes(key)) + .forEach((key) => { + const metadataKey = key.replace(/_/g, '.'); + const path = `metadata/${metadataKey}`; + this.operationsBuilder.remove(this.pathCombiner.getPath(path)); + }); const accessConditionsToSave = []; formData.accessConditions .map((accessConditions) => accessConditions.accessConditionGroup) From 5df2f6f8d5c4b4f697bb3bbd0bbe55279d222704 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 22 Nov 2021 11:15:11 +0100 Subject: [PATCH 04/59] [CST-4947] File description on bitstream can now be deleted --- .../upload/file/section-upload-file.component.spec.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index d16f648e68..cf02dadea7 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -36,7 +36,6 @@ import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/mo import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { dateToISOFormat } from '../../../../shared/date.util'; -import { SubmissionFormModel } from '../../../../core/config/models/config-submission-form.model'; const configMetadataFormMock = { rows: [{ @@ -49,7 +48,7 @@ const configMetadataFormMock = { }] }; -fdescribe('SubmissionSectionUploadFileComponent test suite', () => { +describe('SubmissionSectionUploadFileComponent test suite', () => { let comp: SubmissionSectionUploadFileComponent; let compAsAny: any; @@ -130,8 +129,6 @@ fdescribe('SubmissionSectionUploadFileComponent test suite', () => { testFixture = createTestComponent(html, TestComponent) as ComponentFixture; testComp = testFixture.componentInstance; - // testComp.configMetadataForm = configMetadataFormMock; - // testFixture.detectChanges(); }); afterEach(() => { @@ -139,7 +136,6 @@ fdescribe('SubmissionSectionUploadFileComponent test suite', () => { }); it('should create SubmissionSectionUploadFileComponent', inject([SubmissionSectionUploadFileComponent], (app: SubmissionSectionUploadFileComponent) => { - app.configMetadataForm = Object.assign(new SubmissionFormModel(), configMetadataFormMock); expect(app).toBeDefined(); })); }); @@ -228,6 +224,7 @@ fdescribe('SubmissionSectionUploadFileComponent test suite', () => { it('should save Bitstream File data properly when form is valid', fakeAsync(() => { compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); compAsAny.fileEditComp.formRef = {formGroup: null}; + compAsAny.fileData = fileData; compAsAny.pathCombiner = pathCombiner; const event = new Event('click', null); spyOn(comp, 'switchMode'); From 624f39df1e4cadba6358b1bc4e094627bf27f2e8 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 22 Nov 2021 18:14:45 +0100 Subject: [PATCH 05/59] [CST-4884] Bitstream edit form moved inside modal (test TBD) --- .../section-upload-file-edit.component.html | 26 +- .../section-upload-file-edit.component.ts | 355 ++++++++++++------ .../file/section-upload-file.component.html | 37 +- .../file/section-upload-file.component.ts | 231 ++++-------- 4 files changed, 355 insertions(+), 294 deletions(-) diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html index f6e0646974..2baa6c1555 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html @@ -1,9 +1,21 @@
- + +
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 96725f151e..78bcca876f 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,4 +1,4 @@ -import { ChangeDetectorRef, Component, Input, OnChanges, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core'; import { FormControl } from '@angular/forms'; import { @@ -32,13 +32,23 @@ import { BITSTREAM_METADATA_FORM_GROUP_LAYOUT } from './section-upload-file-edit.model'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; -import { isNotEmpty } from '../../../../../shared/empty.util'; +import { hasNoValue, hasValue, isNotEmpty, isNotNull } from '../../../../../shared/empty.util'; import { SubmissionFormsModel } from '../../../../../core/config/models/config-submission-forms.model'; import { FormFieldModel } from '../../../../../shared/form/builder/models/form-field.model'; import { AccessConditionOption } from '../../../../../core/config/models/config-access-condition-option.model'; import { SubmissionService } from '../../../../submission.service'; import { FormService } from '../../../../../shared/form/form.service'; import { FormComponent } from '../../../../../shared/form/form.component'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { filter, mergeMap, take } from 'rxjs/operators'; +import { dateToISOFormat } from '../../../../../shared/date.util'; +import { SubmissionObject } from '../../../../../core/submission/models/submission-object.model'; +import { WorkspaceitemSectionUploadObject } from '../../../../../core/submission/models/workspaceitem-section-upload.model'; +import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder'; +import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service'; +import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { SectionUploadService } from '../../section-upload.service'; +import { Subscription } from 'rxjs'; /** * This component represents the edit form for bitstream @@ -48,7 +58,31 @@ import { FormComponent } from '../../../../../shared/form/form.component'; styleUrls: ['./section-upload-file-edit.component.scss'], templateUrl: './section-upload-file-edit.component.html', }) -export class SubmissionSectionUploadFileEditComponent implements OnChanges { +export class SubmissionSectionUploadFileEditComponent implements OnInit { + + /** + * Initialize instance variables + * + * @param activeModal + * @param {ChangeDetectorRef} cdr + * @param {FormBuilderService} formBuilderService + * @param {FormService} formService + * @param {SubmissionService} submissionService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SubmissionJsonPatchOperationsService} operationsService + * @param {SectionUploadService} uploadService + */ + constructor( + protected activeModal: NgbActiveModal, + private cdr: ChangeDetectorRef, + private formBuilderService: FormBuilderService, + private formService: FormService, + private submissionService: SubmissionService, + private operationsBuilder: JsonPatchOperationsBuilder, + private operationsService: SubmissionJsonPatchOperationsService, + private uploadService: SectionUploadService, + ) { + } /** * The list of available access condition @@ -113,10 +147,15 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges { @Input() submissionId: string; /** - * The form model - * @type {DynamicFormControlModel[]} + * The list of all available metadata */ - public formModel: DynamicFormControlModel[]; + @Input() formMetadata: string[] = []; + + /** + * The [JsonPatchOperationPathCombiner] object + * @type {JsonPatchOperationPathCombiner} + */ + @Input() pathCombiner: JsonPatchOperationPathCombiner; /** * The FormComponent reference @@ -124,108 +163,18 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges { @ViewChild('formRef') public formRef: FormComponent; /** - * Initialize instance variables - * - * @param {ChangeDetectorRef} cdr - * @param {FormBuilderService} formBuilderService - * @param {FormService} formService - * @param {SubmissionService} submissionService + * The form model + * @type {DynamicFormControlModel[]} */ - constructor(private cdr: ChangeDetectorRef, - private formBuilderService: FormBuilderService, - private formService: FormService, - private submissionService: SubmissionService) { - } + formModel: DynamicFormControlModel[]; - /** - * Dispatch form model init - */ - ngOnChanges() { - if (this.fileData && this.formId) { - this.formModel = this.buildFileEditForm(); - this.cdr.detectChanges(); - } - } + isSaving = false; - /** - * Initialize form model - */ - protected buildFileEditForm() { - const configDescr: FormFieldModel = Object.assign({}, this.configMetadataForm.rows[0].fields[0]); - configDescr.repeatable = false; - const configForm = Object.assign({}, this.configMetadataForm, { - fields: Object.assign([], this.configMetadataForm.rows[0].fields[0], [ - this.configMetadataForm.rows[0].fields[0], - configDescr - ]) - }); - const formModel: DynamicFormControlModel[] = []; - const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG); - metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration( - this.submissionId, - configForm, - this.collectionId, - this.fileData.metadata, - this.submissionService.getSubmissionScope() - ); - formModel.push(new DynamicFormGroupModel(metadataGroupModelConfig, BITSTREAM_METADATA_FORM_GROUP_LAYOUT)); - const accessConditionTypeModelConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG); - const accessConditionsArrayConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG); - const accessConditionTypeOptions = []; + protected subscriptions: Subscription[] = []; - if (this.collectionPolicyType === POLICY_DEFAULT_WITH_LIST) { - for (const accessCondition of this.availableAccessConditionOptions) { - accessConditionTypeOptions.push( - { - label: accessCondition.name, - value: accessCondition.name - } - ); - } - accessConditionTypeModelConfig.options = accessConditionTypeOptions; - - // Dynamically assign of relation in config. For startdate, endDate, groups. - const hasStart = []; - const hasEnd = []; - const hasGroups = []; - this.availableAccessConditionOptions.forEach((condition) => { - const showStart: boolean = condition.hasStartDate === true; - const showEnd: boolean = condition.hasEndDate === true; - const showGroups: boolean = showStart || showEnd; - if (showStart) { - hasStart.push({ id: 'name', value: condition.name }); - } - if (showEnd) { - hasEnd.push({ id: 'name', value: condition.name }); - } - if (showGroups) { - hasGroups.push({ id: 'name', value: condition.name }); - } - }); - const confStart = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart }] }; - const confEnd = { relations: [{ match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd }] }; - - accessConditionsArrayConfig.groupFactory = () => { - const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT); - const startDateConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG, confStart); - const endDateConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG, confEnd); - - const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT); - const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT); - const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG); - accessConditionGroupConfig.group = [type, startDate, endDate]; - return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)]; - }; - - // Number of access conditions blocks in form - accessConditionsArrayConfig.initialCount = isNotEmpty(this.fileData.accessConditions) ? this.fileData.accessConditions.length : 1; - formModel.push( - new DynamicFormArrayModel(accessConditionsArrayConfig, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT) - ); - - } - this.initModelData(formModel); - return formModel; + private static retrieveValueFromField(field: any) { + const temp = Array.isArray(field) ? field[0] : field; + return (temp) ? temp.value : undefined; } /** @@ -262,12 +211,21 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges { * @param event * The event emitted */ - public onChange(event: DynamicFormControlEvent) { + onChange(event: DynamicFormControlEvent) { if (event.model.id === 'name') { this.setOptions(event.model, event.control); } } + onModalClose() { + this.activeModal.dismiss(); + } + + onSubmit() { + this.isSaving = true; + this.saveBitstreamData(); + } + /** * Update `startDate`, 'groupUUID' and 'endDate' model * @@ -323,4 +281,191 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges { } } + /** + * Dispatch form model init + */ + ngOnInit() { + if (this.fileData && this.formId) { + this.formModel = this.buildFileEditForm(); + this.cdr.detectChanges(); + } + } + + ngOnDestroy(): void { + this.unsubscribeAll(); + } + + /** + * Initialize form model + */ + protected buildFileEditForm() { + const configDescr: FormFieldModel = Object.assign({}, this.configMetadataForm.rows[0].fields[0]); + configDescr.repeatable = false; + const configForm = Object.assign({}, this.configMetadataForm, { + fields: Object.assign([], this.configMetadataForm.rows[0].fields[0], [ + this.configMetadataForm.rows[0].fields[0], + configDescr + ]) + }); + const formModel: DynamicFormControlModel[] = []; + const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG); + metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration( + this.submissionId, + configForm, + this.collectionId, + this.fileData.metadata, + this.submissionService.getSubmissionScope() + ); + formModel.push(new DynamicFormGroupModel(metadataGroupModelConfig, BITSTREAM_METADATA_FORM_GROUP_LAYOUT)); + const accessConditionTypeModelConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG); + const accessConditionsArrayConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG); + const accessConditionTypeOptions = []; + + if (this.collectionPolicyType === POLICY_DEFAULT_WITH_LIST) { + for (const accessCondition of this.availableAccessConditionOptions) { + accessConditionTypeOptions.push( + { + label: accessCondition.name, + value: accessCondition.name + } + ); + } + accessConditionTypeModelConfig.options = accessConditionTypeOptions; + + // Dynamically assign of relation in config. For startdate, endDate, groups. + const hasStart = []; + const hasEnd = []; + const hasGroups = []; + this.availableAccessConditionOptions.forEach((condition) => { + const showStart: boolean = condition.hasStartDate === true; + const showEnd: boolean = condition.hasEndDate === true; + const showGroups: boolean = showStart || showEnd; + if (showStart) { + hasStart.push({id: 'name', value: condition.name}); + } + if (showEnd) { + hasEnd.push({id: 'name', value: condition.name}); + } + if (showGroups) { + hasGroups.push({id: 'name', value: condition.name}); + } + }); + const confStart = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasStart}]}; + const confEnd = {relations: [{match: MATCH_ENABLED, operator: OR_OPERATOR, when: hasEnd}]}; + + accessConditionsArrayConfig.groupFactory = () => { + const type = new DynamicSelectModel(accessConditionTypeModelConfig, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT); + const startDateConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG, confStart); + const endDateConfig = Object.assign({}, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG, confEnd); + + const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT); + const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT); + const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG); + accessConditionGroupConfig.group = [type, startDate, endDate]; + return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT)]; + }; + + // Number of access conditions blocks in form + accessConditionsArrayConfig.initialCount = isNotEmpty(this.fileData.accessConditions) ? this.fileData.accessConditions.length : 1; + formModel.push( + new DynamicFormArrayModel(accessConditionsArrayConfig, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT) + ); + + } + this.initModelData(formModel); + return formModel; + } + + /** + * Save bitstream metadata + */ + protected saveBitstreamData() { + // validate form + this.formService.validateAllFormFields(this.formRef.formGroup); + const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe( + take(1), + filter((isValid) => isValid), + mergeMap(() => this.formService.getFormData(this.formId)), + take(1), + mergeMap((formData: any) => { + // collect bitstream metadata + Object.keys((formData.metadata)) + .filter((key) => isNotEmpty(formData.metadata[key])) + .forEach((key) => { + const metadataKey = key.replace(/_/g, '.'); + const path = `metadata/${metadataKey}`; + this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true); + }); + Object.keys((this.fileData.metadata)) + .filter((key) => isNotEmpty(this.fileData.metadata[key])) + .filter((key) => hasNoValue(formData.metadata[key])) + .filter((key) => this.formMetadata.includes(key)) + .forEach((key) => { + const metadataKey = key.replace(/_/g, '.'); + const path = `metadata/${metadataKey}`; + this.operationsBuilder.remove(this.pathCombiner.getPath(path)); + }); + const accessConditionsToSave = []; + formData.accessConditions + .map((accessConditions) => accessConditions.accessConditionGroup) + .filter((accessCondition) => isNotEmpty(accessCondition)) + .forEach((accessCondition) => { + let accessConditionOpt; + + this.availableAccessConditionOptions + .filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value) + .forEach((element) => accessConditionOpt = element); + + if (accessConditionOpt) { + const currentAccessCondition = Object.assign({}, accessCondition); + currentAccessCondition.name = SubmissionSectionUploadFileEditComponent.retrieveValueFromField(accessCondition.name); + + /* When start and end date fields are deactivated, their values may be still present in formData, + therefore it is necessary to delete them if they're not allowed by the current access condition option. */ + if (!accessConditionOpt.hasStartDate) { + delete currentAccessCondition.startDate; + } else if (accessCondition.startDate) { + const startDate = SubmissionSectionUploadFileEditComponent.retrieveValueFromField(accessCondition.startDate); + currentAccessCondition.startDate = dateToISOFormat(startDate); + } + if (!accessConditionOpt.hasEndDate) { + delete currentAccessCondition.endDate; + } else if (accessCondition.endDate) { + const endDate = SubmissionSectionUploadFileEditComponent.retrieveValueFromField(accessCondition.endDate); + currentAccessCondition.endDate = dateToISOFormat(endDate); + } + accessConditionsToSave.push(currentAccessCondition); + } + }); + + if (isNotEmpty(accessConditionsToSave)) { + 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, + this.pathCombiner.rootElement, + this.pathCombiner.subRootElement); + }) + ).subscribe((result: SubmissionObject[]) => { + if (result[0].sections[this.sectionId]) { + const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject); + Object.keys(uploadSection.files) + .filter((key) => uploadSection.files[key].uuid === this.fileId) + .forEach((key) => this.uploadService.updateFileData( + this.submissionId, this.sectionId, this.fileId, uploadSection.files[key]) + ); + } + this.isSaving = false; + this.activeModal.close(); + }); + this.subscriptions.push(saveBitstreamDataSubscription); + } + + private unsubscribeAll() { + this.subscriptions.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + } + } diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 259418c22c..8749c8dcf2 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -9,14 +9,14 @@

{{fileName}} ({{fileData?.sizeBytes | dsFileSize}})

- + - - - - -
- - + 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 9807aecda3..e152d2f651 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,25 +1,23 @@ import { ChangeDetectorRef, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { filter, mergeMap, take } from 'rxjs/operators'; +import { filter } from 'rxjs/operators'; import { DynamicFormControlModel, } from '@ng-dynamic-forms/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { SectionUploadService } from '../section-upload.service'; -import { hasNoValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../../shared/empty.util'; +import { hasValue, isNotUndefined } from '../../../../shared/empty.util'; import { FormService } from '../../../../shared/form/form.service'; import { JsonPatchOperationsBuilder } from '../../../../core/json-patch/builder/json-patch-operations-builder'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { WorkspaceitemSectionUploadFileObject } from '../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { SubmissionFormsModel } from '../../../../core/config/models/config-submission-forms.model'; -import { dateToISOFormat } from '../../../../shared/date.util'; import { SubmissionService } from '../../../submission.service'; import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service'; import { SubmissionJsonPatchOperationsService } from '../../../../core/submission/submission-json-patch-operations.service'; -import { SubmissionObject } from '../../../../core/submission/models/submission-object.model'; -import { WorkspaceitemSectionUploadObject } from '../../../../core/submission/models/workspaceitem-section-upload.model'; import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { NgbModalOptions } from '@ng-bootstrap/ng-bootstrap/modal/modal-config'; /** * This component represents a single bitstream contained in the submission @@ -87,6 +85,13 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { */ @Input() submissionId: string; + /** + * The [[SubmissionSectionUploadFileEditComponent]] reference + * @type {SubmissionSectionUploadFileEditComponent} + */ + @ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent; + + /** * The bitstream's metadata data * @type {WorkspaceitemSectionUploadFileObject} @@ -135,12 +140,6 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { */ protected formMetadata: string[] = []; - /** - * The [[SubmissionSectionUploadFileEditComponent]] reference - * @type {SubmissionSectionUploadFileEditComponent} - */ - @ViewChild(SubmissionSectionUploadFileEditComponent) fileEditComp: SubmissionSectionUploadFileEditComponent; - /** * Initialize instance variables * @@ -153,28 +152,19 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { * @param {SubmissionService} submissionService * @param {SectionUploadService} uploadService */ - constructor(private cdr: ChangeDetectorRef, - private formService: FormService, - private halService: HALEndpointService, - private modalService: NgbModal, - private operationsBuilder: JsonPatchOperationsBuilder, - private operationsService: SubmissionJsonPatchOperationsService, - private submissionService: SubmissionService, - private uploadService: SectionUploadService) { + constructor( + private cdr: ChangeDetectorRef, + private formService: FormService, + private halService: HALEndpointService, + private modalService: NgbModal, + private operationsBuilder: JsonPatchOperationsBuilder, + private operationsService: SubmissionJsonPatchOperationsService, + private submissionService: SubmissionService, + private uploadService: SectionUploadService, + ) { this.readMode = true; } - protected loadFormMetadata() { - this.configMetadataForm.rows.forEach((row) => { - row.fields.forEach((field) => { - field.selectableMetadata.forEach((metadatum) => { - this.formMetadata.push(metadatum.metadata); - }); - }); - } - ); - } - /** * Retrieve bitstream's metadata */ @@ -202,22 +192,6 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { this.loadFormMetadata(); } - /** - * Delete bitstream from submission - */ - protected deleteFile() { - this.operationsBuilder.remove(this.pathCombiner.getPath()); - this.subscriptions.push(this.operationsService.jsonPatchByResourceID( - this.submissionService.getSubmissionObjectLinkName(), - this.submissionId, - this.pathCombiner.rootElement, - this.pathCombiner.subRootElement) - .subscribe(() => { - this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId); - this.processingDelete$.next(false); - })); - } - /** * Show confirmation dialog for delete */ @@ -243,108 +217,6 @@ export class SubmissionSectionUploadFileComponent 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), - filter((isValid) => isValid), - mergeMap(() => this.formService.getFormData(this.formId)), - take(1), - mergeMap((formData: any) => { - // collect bitstream metadata - Object.keys((formData.metadata)) - .filter((key) => isNotEmpty(formData.metadata[key])) - .forEach((key) => { - const metadataKey = key.replace(/_/g, '.'); - const path = `metadata/${metadataKey}`; - this.operationsBuilder.add(this.pathCombiner.getPath(path), formData.metadata[key], true); - }); - Object.keys((this.fileData.metadata)) - .filter((key) => isNotEmpty(this.fileData.metadata[key])) - .filter((key) => hasNoValue(formData.metadata[key])) - .filter((key) => this.formMetadata.includes(key)) - .forEach((key) => { - const metadataKey = key.replace(/_/g, '.'); - const path = `metadata/${metadataKey}`; - this.operationsBuilder.remove(this.pathCombiner.getPath(path)); - }); - const accessConditionsToSave = []; - formData.accessConditions - .map((accessConditions) => accessConditions.accessConditionGroup) - .filter((accessCondition) => isNotEmpty(accessCondition)) - .forEach((accessCondition) => { - let accessConditionOpt; - - this.availableAccessConditionOptions - .filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value) - .forEach((element) => accessConditionOpt = element); - - if (accessConditionOpt) { - const currentAccessCondition = Object.assign({}, accessCondition); - currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name); - - /* When start and end date fields are deactivated, their values may be still present in formData, - therefore it is necessary to delete them if they're not allowed by the current access condition option. */ - if (!accessConditionOpt.hasStartDate) { - delete currentAccessCondition.startDate; - } else if (accessCondition.startDate) { - const startDate = this.retrieveValueFromField(accessCondition.startDate); - currentAccessCondition.startDate = dateToISOFormat(startDate); - } - if (!accessConditionOpt.hasEndDate) { - delete currentAccessCondition.endDate; - } else if (accessCondition.endDate) { - const endDate = this.retrieveValueFromField(accessCondition.endDate); - currentAccessCondition.endDate = dateToISOFormat(endDate); - } - accessConditionsToSave.push(currentAccessCondition); - } - }); - - if (isNotEmpty(accessConditionsToSave)) { - 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, - this.pathCombiner.rootElement, - this.pathCombiner.subRootElement); - }) - ).subscribe((result: SubmissionObject[]) => { - if (result[0].sections[this.sectionId]) { - const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject); - Object.keys(uploadSection.files) - .filter((key) => uploadSection.files[key].uuid === this.fileId) - .forEach((key) => this.uploadService.updateFileData( - this.submissionId, this.sectionId, this.fileId, uploadSection.files[key]) - ); - } - this.switchMode(); - })); - } - - /** - * 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 */ @@ -353,4 +225,67 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { this.cdr.detectChanges(); } + editBitstreamData() { + + const options: NgbModalOptions = { + size: 'xl', + backdrop: 'static', + }; + + const activeModal = this.modalService.open(SubmissionSectionUploadFileEditComponent, options); + + activeModal.componentInstance.availableAccessConditionOptions = this.availableAccessConditionOptions; + activeModal.componentInstance.collectionId = this.collectionId; + activeModal.componentInstance.collectionPolicyType = this.collectionPolicyType; + activeModal.componentInstance.configMetadataForm = this.configMetadataForm; + activeModal.componentInstance.fileData = this.fileData; + activeModal.componentInstance.fileId = this.fileId; + activeModal.componentInstance.fileIndex = this.fileIndex; + activeModal.componentInstance.formId = this.formId; + activeModal.componentInstance.sectionId = this.sectionId; + activeModal.componentInstance.formMetadata = this.formMetadata; + activeModal.componentInstance.pathCombiner = this.pathCombiner; + activeModal.componentInstance.submissionId = this.submissionId; + + /*activeModal.componentInstance.saveBitstreamDataEvent.subscribe((res) => { + console.log(JSON.stringify(res)); + });*/ + + } + + ngOnDestroy(): void { + this.unsubscribeAll(); + } + + unsubscribeAll() { + this.subscriptions.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + } + + protected loadFormMetadata() { + this.configMetadataForm.rows.forEach((row) => { + row.fields.forEach((field) => { + field.selectableMetadata.forEach((metadatum) => { + this.formMetadata.push(metadatum.metadata); + }); + }); + } + ); + } + + /** + * Delete bitstream from submission + */ + protected deleteFile() { + this.operationsBuilder.remove(this.pathCombiner.getPath()); + this.subscriptions.push(this.operationsService.jsonPatchByResourceID( + this.submissionService.getSubmissionObjectLinkName(), + this.submissionId, + this.pathCombiner.rootElement, + this.pathCombiner.subRootElement) + .subscribe(() => { + this.uploadService.removeUploadedFile(this.submissionId, this.sectionId, this.fileId); + this.processingDelete$.next(false); + })); + } + } From 31442f36a3fbe9101de5b03a45f672722f48d48b Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 22 Nov 2021 19:11:58 +0100 Subject: [PATCH 06/59] [CST-4884] Bitstream edit form moved inside modal (test WIP) --- ...section-upload-file-edit.component.spec.ts | 43 +++++++++++++++++-- .../section-upload-file-edit.component.ts | 16 +++---- .../section-upload-file.component.spec.ts | 21 +++------ 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index a644cf8270..9203930ef5 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -30,15 +30,31 @@ import { FormService } from '../../../../../shared/form/form.service'; import { getMockFormService } from '../../../../../shared/mocks/form-service.mock'; import { Group } from '../../../../../core/eperson/models/group.model'; import { createTestComponent } from '../../../../../shared/testing/utils.test'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder'; +import { SubmissionJsonPatchOperationsServiceStub } from '../../../../../shared/testing/submission-json-patch-operations-service.stub'; +import { SubmissionJsonPatchOperationsService } from '../../../../../core/submission/submission-json-patch-operations.service'; +import { SectionUploadService } from '../../section-upload.service'; +import { getMockSectionUploadService } from '../../../../../shared/mocks/section-upload.service.mock'; +import { FormFieldMetadataValueObject } from '../../../../../shared/form/builder/models/form-field-metadata-value.model'; -describe('SubmissionSectionUploadFileEditComponent test suite', () => { +const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { + add: jasmine.createSpy('add'), + replace: jasmine.createSpy('replace'), + remove: jasmine.createSpy('remove'), +}); + +fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { let comp: SubmissionSectionUploadFileEditComponent; let compAsAny: any; let fixture: ComponentFixture; let submissionServiceStub: SubmissionServiceStub; let formbuilderService: any; + let operationsBuilder: any; + let operationsService: any; + const submissionJsonPatchOperationsServiceStub = new SubmissionJsonPatchOperationsServiceStub(); const submissionId = mockSubmissionId; const sectionId = 'upload'; const collectionId = mockSubmissionCollectionId; @@ -66,9 +82,14 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { providers: [ { provide: FormService, useValue: getMockFormService() }, { provide: SubmissionService, useClass: SubmissionServiceStub }, + { provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsServiceStub }, + { provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder }, + { provide: SectionUploadService, useValue: getMockSectionUploadService() }, FormBuilderService, ChangeDetectorRef, - SubmissionSectionUploadFileEditComponent + SubmissionSectionUploadFileEditComponent, + NgbModal, + NgbActiveModal, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -114,6 +135,8 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { compAsAny = comp; submissionServiceStub = TestBed.inject(SubmissionService as any); formbuilderService = TestBed.inject(FormBuilderService); + operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder); + operationsService = TestBed.inject(SubmissionJsonPatchOperationsService); comp.submissionId = submissionId; comp.collectionId = collectionId; @@ -135,7 +158,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { comp.fileData = fileData; comp.formId = 'testFileForm'; - comp.ngOnChanges(); + comp.ngOnInit(); expect(comp.formModel).toBeDefined(); expect(comp.formModel.length).toBe(2); @@ -165,7 +188,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { comp.fileData = fileData; comp.formId = 'testFileForm'; - comp.ngOnChanges(); + comp.ngOnInit(); const model: DynamicSelectModel = formbuilderService.findById('name', comp.formModel, 0); const formGroup = formbuilderService.createFormGroup(comp.formModel); @@ -186,6 +209,18 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { comp.setOptions(model, control); expect(formbuilderService.findById).toHaveBeenCalledWith('startDate', (model.parent as DynamicFormArrayGroupModel).group); }); + + it('should retrieve Value From Field properly', () => { + let field; + expect(compAsAny.retrieveValueFromField(field)).toBeUndefined(); + + field = new FormFieldMetadataValueObject('test'); + expect(compAsAny.retrieveValueFromField(field)).toBe('test'); + + field = [new FormFieldMetadataValueObject('test')]; + expect(compAsAny.retrieveValueFromField(field)).toBe('test'); + }); + }); }); 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 78bcca876f..eb8fbf95d0 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 @@ -172,11 +172,6 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { protected subscriptions: Subscription[] = []; - private static retrieveValueFromField(field: any) { - const temp = Array.isArray(field) ? field[0] : field; - return (temp) ? temp.value : undefined; - } - /** * Initialize form model values * @@ -295,6 +290,11 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { this.unsubscribeAll(); } + protected retrieveValueFromField(field: any) { + const temp = Array.isArray(field) ? field[0] : field; + return (temp) ? temp.value : undefined; + } + /** * Initialize form model */ @@ -418,20 +418,20 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { if (accessConditionOpt) { const currentAccessCondition = Object.assign({}, accessCondition); - currentAccessCondition.name = SubmissionSectionUploadFileEditComponent.retrieveValueFromField(accessCondition.name); + currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name); /* When start and end date fields are deactivated, their values may be still present in formData, therefore it is necessary to delete them if they're not allowed by the current access condition option. */ if (!accessConditionOpt.hasStartDate) { delete currentAccessCondition.startDate; } else if (accessCondition.startDate) { - const startDate = SubmissionSectionUploadFileEditComponent.retrieveValueFromField(accessCondition.startDate); + const startDate = this.retrieveValueFromField(accessCondition.startDate); currentAccessCondition.startDate = dateToISOFormat(startDate); } if (!accessConditionOpt.hasEndDate) { delete currentAccessCondition.endDate; } else if (accessCondition.endDate) { - const endDate = SubmissionSectionUploadFileEditComponent.retrieveValueFromField(accessCondition.endDate); + const endDate = this.retrieveValueFromField(accessCondition.endDate); currentAccessCondition.endDate = dateToISOFormat(endDate); } accessConditionsToSave.push(currentAccessCondition); diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index cf02dadea7..a8ab34d866 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -48,7 +48,7 @@ const configMetadataFormMock = { }] }; -describe('SubmissionSectionUploadFileComponent test suite', () => { +fdescribe('SubmissionSectionUploadFileComponent test suite', () => { let comp: SubmissionSectionUploadFileComponent; let compAsAny: any; @@ -221,7 +221,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { pathCombiner.subRootElement); }); - it('should save Bitstream File data properly when form is valid', fakeAsync(() => { + /*it('should save Bitstream File data properly when form is valid', fakeAsync(() => { compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); compAsAny.fileEditComp.formRef = {formGroup: null}; compAsAny.fileData = fileData; @@ -275,9 +275,9 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { expect(comp.switchMode).toHaveBeenCalled(); expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]); - })); + }));*/ - it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { + /*it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); compAsAny.fileEditComp.formRef = {formGroup: null}; compAsAny.pathCombiner = pathCombiner; @@ -289,18 +289,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { expect(comp.switchMode).not.toHaveBeenCalled(); expect(uploadService.updateFileData).not.toHaveBeenCalled(); - })); - - it('should retrieve Value From Field properly', () => { - let field; - expect(compAsAny.retrieveValueFromField(field)).toBeUndefined(); - - field = new FormFieldMetadataValueObject('test'); - expect(compAsAny.retrieveValueFromField(field)).toBe('test'); - - field = [new FormFieldMetadataValueObject('test')]; - expect(compAsAny.retrieveValueFromField(field)).toBe('test'); - }); + }));*/ it('should switch read mode', () => { comp.readMode = false; From ffff337abafdca88a594ab8d910af4576f4726df Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 23 Nov 2021 16:43:53 +0100 Subject: [PATCH 07/59] 85123: Remove favicon --- src/assets/images/favicon.ico | Bin 3262 -> 0 bytes src/index.csr.html | 1 - src/index.html | 1 - 3 files changed, 2 deletions(-) delete mode 100644 src/assets/images/favicon.ico diff --git a/src/assets/images/favicon.ico b/src/assets/images/favicon.ico deleted file mode 100644 index c73ad96b2699f44ca88a0e333bc6567c639a8610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3262 zcmeH}p>GsH6vnrK1k;2nf+80XAmKkiRGc_bfk0G1Qc+QHBo!wr7PycV6)P%6RE(%t zQL&<8Ma7a-jA+6L#E6O^sTff)qGAQf@|&C9wOP#NXaLda?sVtvo9}(|y?HyABuo7D zdI|mGq*zXpM@f>r;Kr1^<_`AIQr>mj&!)M%{-FYeRePpBw5pjV;=9&|MNyTd_a0&w z&MBp442>~yi2rd_#aZXpmPK(?mN?_dM5af}K;;Kk?U-^~7ZC*Tle!i{?BzKY!j02( z7X8eat<6n2d5awf=iJU{R8>_WB_XijvppOF0+S6TBkY@6ocRzPe?shL+01z~91=_9 zd0bJJrl93n27ml<+ZQ5&#NJvDc(+#P7flc(9gPm8)LKV0qji?0Ji|5u z)^$yET6BPCTp;409tiLU|5PbJ)|Eh+O0#3|S9K!SF5F;VxnbMgR(eC>HM*&iQ-gq}Dj8zVU-P$I@5%hL(I)76xbHP!|!uZ^Y50Iw-)^I`12aJ@iKI znQGw>*ZBGhpFMx-t@8HRX-YCg(Jaj_79uy*0DUHex29je8PsLdyj5agWTqEi0@<|Q zAr2`ZgfO) DSpace - diff --git a/src/index.html b/src/index.html index 491a2d319c..ddd448f289 100644 --- a/src/index.html +++ b/src/index.html @@ -6,7 +6,6 @@ DSpace - From a5a91d51399a5a197ae4f2d9bf8929bd40c04477 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 23 Nov 2021 17:03:25 +0100 Subject: [PATCH 08/59] 85123: Add new favicon files --- .../favicons/android-chrome-192x192.png | Bin 0 -> 11844 bytes .../favicons/android-chrome-512x512.png | Bin 0 -> 33567 bytes .../images/favicons/apple-touch-icon.png | Bin 0 -> 2371 bytes .../dspace/assets/images/favicons/favicon.ico | Bin 0 -> 15086 bytes .../dspace/assets/images/favicons/favicon.svg | 7 +++++++ .../images/favicons/manifest.webmanifest | 19 ++++++++++++++++++ 6 files changed, 26 insertions(+) create mode 100644 src/themes/dspace/assets/images/favicons/android-chrome-192x192.png create mode 100644 src/themes/dspace/assets/images/favicons/android-chrome-512x512.png create mode 100644 src/themes/dspace/assets/images/favicons/apple-touch-icon.png create mode 100644 src/themes/dspace/assets/images/favicons/favicon.ico create mode 100644 src/themes/dspace/assets/images/favicons/favicon.svg create mode 100644 src/themes/dspace/assets/images/favicons/manifest.webmanifest diff --git a/src/themes/dspace/assets/images/favicons/android-chrome-192x192.png b/src/themes/dspace/assets/images/favicons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..1aaffb1b90e56ff30d06451586641e3ddaf0fbd6 GIT binary patch literal 11844 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 QUbzK;LY#+JS^3=i2VZyi4FCWD literal 0 HcmV?d00001 diff --git a/src/themes/dspace/assets/images/favicons/favicon.ico b/src/themes/dspace/assets/images/favicons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a5dfd5e3226ca4dd5990a674f5b053afbe1f4d38 GIT binary patch literal 15086 zcmdU$50F(=9mnso$TR=2s3;)Nt|>}tfn!RcvVi8K*jSoY4mn!XXpQ~BY&3QDG0vDx z777`El8nm6X-1m_6U&LUF)PQ)NFhTBBh4fgp=_0v*XMip{$7W(=iYbkdv9U)&3yKE z?(h80@Ao_B-h1x3=Ry>M)#10^{4EEalV8Cs2%Zb{W?NJ)+XMMq zPS2C)#n9@f`(1Huwj)G1AJq7djiv|51F@e1^IG$tvS(7)*l)LGyoF?xtUXjmF20i^^AM`<|?v z>Z!x$c=$<z*AsY}5L-kha6>$up}BK92$2A8B!=*E;62pzAoxE~`%S*{rNw z>aD}?XF$*2toV0Re-zvUMdtwX`#aiBsW;EGcKAF3o&k#!l255UHf?Ywd>phsHs(?O z9NYpAL)x{bbNyi`wDwKQJ;`Yw{M`$oyiK<~%ZFR1+(3O?1)(+HYv|X)^`O4Y@tDo= zXxC@0GWL2muv|m(&00SEdi;%q8zAd?({-ypy7X{-AB^*TrX6Fe^B&5(s!MB|rN?Ip zH1|~fjgS5FY>2$zAiljcpz|n8v?cFn9HDP~=Y@TAw^I6!RzN{a|Swxoge) z(X2V&t;4qI&o6u)0^fkwA#}a#-AQ$tS6#MqthM@KZjM=HY@3olep*0zp9jAHjiaBt z^3&Q*>(#TN6J{i+JPq2i^7ZSmUjlv{_qbPWu+clVa{D}d!9(lG74UmVTeCez-8rzY zpF_RJ_!SMes-6zAyz&0=NBC zjyqt#U_(cV%|)PjW@`XFV}1t5IZdiX^hd%Ipmm?y{soRVg4$jSdhZB+(?-7qYz|A4 zw^f5W1=<}AdG=Xl^EH^@G>?nu(qg*$RoKPf_1*U9^2Rq`kI-hfccYx~S2roX^3eG@ z(fY9$;wy)47^@s|#=jrC5tg$_bYnp0RnNWU4tG-4dN^%gMmdz_2WjUfOUhU3eQ5cZ z#8&t8!7vxJml5*)H2UM9)?R|zZiCaze-TN$li?}wbI@8u_ol7~zb^$xfK z)`8h;eYgfb2{U0joB|r-)I9_0M!@}GeoV&uj9LAljn+^Pcx^QHw^T3Uw^cHJFEq_ zzr7r9hgU#zv*aSE>;9wN{xHtL)`MR{r*XB|J(M+9>bdf6(6|V#uMY69dpoAC1Z%+j znADtn255fyi-Y!1)hGAar1|E{u)q1uD`_(ksqhS#FO!7I*}>$ew)?UHDmp4ejmhN zJXh?6_wb;q{*LtpN%!2;6n0#u zT*4N6wTautK4O3JDc_3Gu9*61pF-Ob9Z=n;h`)fKaDMdV)1W!{JBfV1RdakOd>!t9 z+hGCBfNK9KYn^KMpX5UjKeLcJkADZN*EOR(g~gEeo~Su7-cdoSy{<8})4XbaB!378 zc{=GeKOL$*Aoct`2QGwsQbJvuq4piC)*#r=SPaW*y9dtKe$T zILg2DifPwO_1TI}bL)DDa|rnVAayUp8=$e$v-DE4qdXEGg*a!^n?$d79?k1+pRRH0 z^R!=6PkY)7g9|{gO#8@Yc4$xbiW-9)%`BY<6r}*e~R&H zQnvo;{GDm>T!~)i?{cu`*B>ccJClDxXV=dGls^C$!N=fMcnM@HAM=y4wUvAf{OebG zUBj-Oo~xr^ho{%tCB9yeq5DT;x)AI+r+3z+und;K+16*ux_@ti<)Au^@pPy@^A|D; gme|27vDaIA7J8y+3-nyQJJP8xwvjDu7P5c(KTAbi>Hq)$ literal 0 HcmV?d00001 diff --git a/src/themes/dspace/assets/images/favicons/favicon.svg b/src/themes/dspace/assets/images/favicons/favicon.svg new file mode 100644 index 0000000000..8ea65cb72f --- /dev/null +++ b/src/themes/dspace/assets/images/favicons/favicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/themes/dspace/assets/images/favicons/manifest.webmanifest b/src/themes/dspace/assets/images/favicons/manifest.webmanifest new file mode 100644 index 0000000000..1784ba2b4c --- /dev/null +++ b/src/themes/dspace/assets/images/favicons/manifest.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "DSpace", + "short_name": "DSpace", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#091119", + "background_color": "#091119", + "display": "standalone" +} From 99a2cf926a02a0acdbdac1d60ecb2a4dc99dff7b Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Tue, 23 Nov 2021 19:36:49 +0100 Subject: [PATCH 09/59] 85123: WIP: Support for theme-specific head tags --- src/app/app.component.ts | 62 +++++++++++++++++++++++--- src/config/theme.model.ts | 22 +++++++++ src/environments/environment.common.ts | 34 +++++++++++++- 3 files changed, 111 insertions(+), 7 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6f06a84144..f138753596 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -31,12 +31,12 @@ import { AuthService } from './core/auth/auth.service'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; import { MenuService } from './shared/menu/menu.service'; import { HostWindowService } from './shared/host-window.service'; -import { ThemeConfig } from '../config/theme.model'; +import {HeadTagConfig, ThemeConfig} from '../config/theme.model'; import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; import { environment } from '../environments/environment'; import { models } from './core/core.module'; import { LocaleService } from './core/locale/locale.service'; -import { hasValue, isNotEmpty } from './shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from './shared/empty.util'; import { KlaroService } from './shared/cookies/klaro.service'; import { GoogleAnalyticsService } from './statistics/google-analytics.service'; import { DOCUMENT, isPlatformBrowser } from '@angular/common'; @@ -115,11 +115,11 @@ export class AppComponent implements OnInit, AfterViewInit { this.isThemeCSSLoading$.next(true); } if (hasValue(themeName)) { - this.setThemeCss(themeName); + this.loadGlobalThemeConfig(themeName); } else if (hasValue(DEFAULT_THEME_CONFIG)) { - this.setThemeCss(DEFAULT_THEME_CONFIG.name); + this.loadGlobalThemeConfig(DEFAULT_THEME_CONFIG.name); } else { - this.setThemeCss(BASE_THEME_NAME); + this.loadGlobalThemeConfig(BASE_THEME_NAME); } }); @@ -233,6 +233,11 @@ export class AppComponent implements OnInit, AfterViewInit { } } + private loadGlobalThemeConfig(themeName: string): void { + this.setThemeCss(themeName); + this.setHeadTags(themeName); + } + /** * Update the theme css file in * @@ -243,7 +248,7 @@ export class AppComponent implements OnInit, AfterViewInit { const head = this.document.getElementsByTagName('head')[0]; // Array.from to ensure we end up with an array, not an HTMLCollection, which would be // automatically updated if we add nodes later - const currentThemeLinks = Array.from(this.document.getElementsByClassName('theme-css')); + const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css')); const link = this.document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('type', 'text/css'); @@ -265,6 +270,51 @@ export class AppComponent implements OnInit, AfterViewInit { head.appendChild(link); } + private setHeadTags(themeName: string): void { + const head = this.document.getElementsByTagName('head')[0]; + + // clear head tags + const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag')); + if (isNotEmpty(currentHeadTags)) { + currentHeadTags.forEach((currentHeadTag: any) => currentHeadTag.remove()); + } + + // create new head tags (not yet added to DOM) + const headTagFragment = document.createDocumentFragment(); + this.createHeadTags(themeName) + .forEach(newHeadTag => headTagFragment.appendChild(newHeadTag)); + + // add new head tags to DOM + head.appendChild(headTagFragment); + } + + private createHeadTags(themeName: string): HTMLElement[] { + const themeConfig = this.themeService.getThemeConfigFor(themeName); + const headTagConfigs = themeConfig?.headTags; + + // if the current theme does not have head tags, we inherit the head tags of the parent + if (isEmpty(headTagConfigs)) { + const parentThemeName = themeConfig.extends; + return isNotEmpty(parentThemeName) ? this.createHeadTags(parentThemeName) : []; + } + + return headTagConfigs.map(this.createHeadTag.bind(this)); + } + + private createHeadTag(themeHeadTag: HeadTagConfig): HTMLElement { + const tag = this.document.createElement(themeHeadTag.tagName); + + if (isNotEmpty(themeHeadTag.attributes)) { + Object.entries(themeHeadTag.attributes) + .forEach(([key, value]) => tag.setAttribute(key, value)); + } + + // 'class' attribute should always be 'theme-head-tag' for removal + tag.setAttribute('class', 'theme-head-tag'); + + return tag; + } + private trackIdleModal() { const isIdle$ = this.authService.isUserIdle(); const isAuthenticated$ = this.authService.isAuthenticated(); diff --git a/src/config/theme.model.ts b/src/config/theme.model.ts index 0130b5ffd8..d65118d5d4 100644 --- a/src/config/theme.model.ts +++ b/src/config/theme.model.ts @@ -12,6 +12,28 @@ export interface NamedThemeConfig extends Config { * its ancestor theme(s) will be checked recursively before falling back to the default theme. */ extends?: string; + + /** + * A list of HTML tags that should be added to the HEAD section of the document, whenever this theme is active. + */ + headTags?: HeadTagConfig[]; +} + +/** + * Interface that represents a single theme-specific HTML tag in the HEAD section of the page. + */ +export interface HeadTagConfig extends Config { + /** + * The name of the HTML tag + */ + tagName: string; + + /** + * The attributes on the HTML tag + */ + attributes?: { + [key: string]: string; + }; } export interface RegExThemeConfig extends NamedThemeConfig { diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index b1cbd699a3..b8b7c31092 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -292,7 +292,39 @@ export const environment: GlobalConfig = { { // The default dspace theme - name: 'dspace' + name: 'dspace', + headTags: [ + { + tagName: 'link', + attributes: { + 'rel': 'icon', + 'href': 'assets/dspace/images/favicons/favicon.ico', + 'sizes': 'any', + } + }, + { + tagName: 'link', + attributes: { + 'rel': 'icon', + 'href': 'assets/dspace/images/favicons/favicon.svg', + 'type': 'image/svg+xml', + } + }, + { + tagName: 'link', + attributes: { + 'rel': 'apple-touch-icon', + 'href': 'assets/dspace/images/favicons/apple-touch-icon.png', + } + }, + { + tagName: 'link', + attributes: { + 'rel': 'manifest', + 'href': 'assets/dspace/images/favicons/manifest.webmanifest', + } + }, + ] }, ], // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video"). From 893a306da066d18064fe9e9ae0b75d1454261d12 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 24 Nov 2021 12:18:45 +0100 Subject: [PATCH 10/59] 85123: Fallback to favicon of default theme and src/assets/images/favicon.ico --- src/app/app.component.ts | 38 +++++++++++++++++++++++++++------- src/assets/images/favicon.ico | Bin 0 -> 15086 bytes 2 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 src/assets/images/favicon.ico diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f138753596..9dc2b5a5ab 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -31,7 +31,7 @@ import { AuthService } from './core/auth/auth.service'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; import { MenuService } from './shared/menu/menu.service'; import { HostWindowService } from './shared/host-window.service'; -import {HeadTagConfig, ThemeConfig} from '../config/theme.model'; +import { HeadTagConfig, ThemeConfig } from '../config/theme.model'; import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; import { environment } from '../environments/environment'; import { models } from './core/core.module'; @@ -292,20 +292,44 @@ export class AppComponent implements OnInit, AfterViewInit { const themeConfig = this.themeService.getThemeConfigFor(themeName); const headTagConfigs = themeConfig?.headTags; - // if the current theme does not have head tags, we inherit the head tags of the parent if (isEmpty(headTagConfigs)) { const parentThemeName = themeConfig.extends; - return isNotEmpty(parentThemeName) ? this.createHeadTags(parentThemeName) : []; + if (isNotEmpty(parentThemeName)) { + // inherit the head tags of the parent theme + return this.createHeadTags(parentThemeName); + } + + const defaultThemeName = DEFAULT_THEME_CONFIG.name; + if ( + isEmpty(defaultThemeName) || + themeName === defaultThemeName || + themeName === BASE_THEME_NAME + ) { + // last resort, use fallback favicon.ico + return [ + this.createHeadTag({ + 'tagName': 'link', + 'attributes': { + 'rel': 'icon', + 'href': 'assets/images/favicon.ico', + 'sizes': 'any', + } + }) + ]; + } + + // inherit the head tags of the default theme + return this.createHeadTags(DEFAULT_THEME_CONFIG.name); } return headTagConfigs.map(this.createHeadTag.bind(this)); } - private createHeadTag(themeHeadTag: HeadTagConfig): HTMLElement { - const tag = this.document.createElement(themeHeadTag.tagName); + private createHeadTag(headTagConfig: HeadTagConfig): HTMLElement { + const tag = this.document.createElement(headTagConfig.tagName); - if (isNotEmpty(themeHeadTag.attributes)) { - Object.entries(themeHeadTag.attributes) + if (isNotEmpty(headTagConfig.attributes)) { + Object.entries(headTagConfig.attributes) .forEach(([key, value]) => tag.setAttribute(key, value)); } diff --git a/src/assets/images/favicon.ico b/src/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a5dfd5e3226ca4dd5990a674f5b053afbe1f4d38 GIT binary patch literal 15086 zcmdU$50F(=9mnso$TR=2s3;)Nt|>}tfn!RcvVi8K*jSoY4mn!XXpQ~BY&3QDG0vDx z777`El8nm6X-1m_6U&LUF)PQ)NFhTBBh4fgp=_0v*XMip{$7W(=iYbkdv9U)&3yKE z?(h80@Ao_B-h1x3=Ry>M)#10^{4EEalV8Cs2%Zb{W?NJ)+XMMq zPS2C)#n9@f`(1Huwj)G1AJq7djiv|51F@e1^IG$tvS(7)*l)LGyoF?xtUXjmF20i^^AM`<|?v z>Z!x$c=$<z*AsY}5L-kha6>$up}BK92$2A8B!=*E;62pzAoxE~`%S*{rNw z>aD}?XF$*2toV0Re-zvUMdtwX`#aiBsW;EGcKAF3o&k#!l255UHf?Ywd>phsHs(?O z9NYpAL)x{bbNyi`wDwKQJ;`Yw{M`$oyiK<~%ZFR1+(3O?1)(+HYv|X)^`O4Y@tDo= zXxC@0GWL2muv|m(&00SEdi;%q8zAd?({-ypy7X{-AB^*TrX6Fe^B&5(s!MB|rN?Ip zH1|~fjgS5FY>2$zAiljcpz|n8v?cFn9HDP~=Y@TAw^I6!RzN{a|Swxoge) z(X2V&t;4qI&o6u)0^fkwA#}a#-AQ$tS6#MqthM@KZjM=HY@3olep*0zp9jAHjiaBt z^3&Q*>(#TN6J{i+JPq2i^7ZSmUjlv{_qbPWu+clVa{D}d!9(lG74UmVTeCez-8rzY zpF_RJ_!SMes-6zAyz&0=NBC zjyqt#U_(cV%|)PjW@`XFV}1t5IZdiX^hd%Ipmm?y{soRVg4$jSdhZB+(?-7qYz|A4 zw^f5W1=<}AdG=Xl^EH^@G>?nu(qg*$RoKPf_1*U9^2Rq`kI-hfccYx~S2roX^3eG@ z(fY9$;wy)47^@s|#=jrC5tg$_bYnp0RnNWU4tG-4dN^%gMmdz_2WjUfOUhU3eQ5cZ z#8&t8!7vxJml5*)H2UM9)?R|zZiCaze-TN$li?}wbI@8u_ol7~zb^$xfK z)`8h;eYgfb2{U0joB|r-)I9_0M!@}GeoV&uj9LAljn+^Pcx^QHw^T3Uw^cHJFEq_ zzr7r9hgU#zv*aSE>;9wN{xHtL)`MR{r*XB|J(M+9>bdf6(6|V#uMY69dpoAC1Z%+j znADtn255fyi-Y!1)hGAar1|E{u)q1uD`_(ksqhS#FO!7I*}>$ew)?UHDmp4ejmhN zJXh?6_wb;q{*LtpN%!2;6n0#u zT*4N6wTautK4O3JDc_3Gu9*61pF-Ob9Z=n;h`)fKaDMdV)1W!{JBfV1RdakOd>!t9 z+hGCBfNK9KYn^KMpX5UjKeLcJkADZN*EOR(g~gEeo~Su7-cdoSy{<8})4XbaB!378 zc{=GeKOL$*Aoct`2QGwsQbJvuq4piC)*#r=SPaW*y9dtKe$T zILg2DifPwO_1TI}bL)DDa|rnVAayUp8=$e$v-DE4qdXEGg*a!^n?$d79?k1+pRRH0 z^R!=6PkY)7g9|{gO#8@Yc4$xbiW-9)%`BY<6r}*e~R&H zQnvo;{GDm>T!~)i?{cu`*B>ccJClDxXV=dGls^C$!N=fMcnM@HAM=y4wUvAf{OebG zUBj-Oo~xr^ho{%tCB9yeq5DT;x)AI+r+3z+und;K+16*ux_@ti<)Au^@pPy@^A|D; gme|27vDaIA7J8y+3-nyQJJP8xwvjDu7P5c(KTAbi>Hq)$ literal 0 HcmV?d00001 From 5e8813f5b6c811ceb611eebdcd128dc3d67a4d8b Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 24 Nov 2021 13:12:01 +0100 Subject: [PATCH 11/59] [CST-4884] Bitstream edit form moved inside modal (test WIP) --- ...section-upload-file-edit.component.spec.ts | 89 ++++++++- .../section-upload-file-edit.component.ts | 183 +++++++++--------- .../section-upload-file.component.spec.ts | 6 +- 3 files changed, 179 insertions(+), 99 deletions(-) diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index 9203930ef5..866277108f 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; -import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing'; +import { waitForAsync, ComponentFixture, inject, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { BrowserModule } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; @@ -17,18 +17,18 @@ import { SubmissionService } from '../../../../submission.service'; import { SubmissionSectionUploadFileEditComponent } from './section-upload-file-edit.component'; import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload.component'; import { - mockGroup, mockSubmissionCollectionId, mockSubmissionId, mockUploadConfigResponse, mockUploadConfigResponseMetadata, - mockUploadFiles + mockUploadFiles, + mockFileFormData, + mockSubmissionObject, } from '../../../../../shared/mocks/submission.mock'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormComponent } from '../../../../../shared/form/form.component'; import { FormService } from '../../../../../shared/form/form.service'; import { getMockFormService } from '../../../../../shared/mocks/form-service.mock'; -import { Group } from '../../../../../core/eperson/models/group.model'; import { createTestComponent } from '../../../../../shared/testing/utils.test'; import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { JsonPatchOperationsBuilder } from '../../../../../core/json-patch/builder/json-patch-operations-builder'; @@ -37,6 +37,9 @@ import { SubmissionJsonPatchOperationsService } from '../../../../../core/submis import { SectionUploadService } from '../../section-upload.service'; import { getMockSectionUploadService } from '../../../../../shared/mocks/section-upload.service.mock'; import { FormFieldMetadataValueObject } from '../../../../../shared/form/builder/models/form-field-metadata-value.model'; +import { JsonPatchOperationPathCombiner } from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { dateToISOFormat } from '../../../../../shared/date.util'; +import { of } from 'rxjs'; const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { add: jasmine.createSpy('add'), @@ -44,6 +47,8 @@ const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { remove: jasmine.createSpy('remove'), }); +const formMetadataMock = ['dc.title', 'dc.description']; + fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { let comp: SubmissionSectionUploadFileEditComponent; @@ -53,6 +58,8 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { let formbuilderService: any; let operationsBuilder: any; let operationsService: any; + let formService: any; + let uploadService: any; const submissionJsonPatchOperationsServiceStub = new SubmissionJsonPatchOperationsServiceStub(); const submissionId = mockSubmissionId; @@ -64,6 +71,7 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { const fileIndex = '0'; const fileId = '123456-test-upload'; const fileData: any = mockUploadFiles[0]; + const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -137,6 +145,8 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { formbuilderService = TestBed.inject(FormBuilderService); operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder); operationsService = TestBed.inject(SubmissionJsonPatchOperationsService); + formService = TestBed.inject(FormService); + uploadService = TestBed.inject(SectionUploadService); comp.submissionId = submissionId; comp.collectionId = collectionId; @@ -146,6 +156,7 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { comp.fileIndex = fileIndex; comp.fileId = fileId; comp.configMetadataForm = configMetadataForm; + comp.formMetadata = formMetadataMock; }); afterEach(() => { @@ -221,6 +232,76 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { expect(compAsAny.retrieveValueFromField(field)).toBe('test'); }); + it('should save Bitstream File data properly when form is valid', fakeAsync(() => { + compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); + compAsAny.fileEditComp.formRef = {formGroup: null}; + compAsAny.fileData = fileData; + compAsAny.pathCombiner = pathCombiner; + // const event = new Event('click', null); + // spyOn(comp, 'switchMode'); + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(of(true)); + formService.getFormData.and.returnValue(of(mockFileFormData)); + + const response = [ + Object.assign(mockSubmissionObject, { + sections: { + upload: { + files: mockUploadFiles + } + } + }) + ]; + operationsService.jsonPatchByResourceID.and.returnValue(of(response)); + + const accessConditionsToSave = [ + { name: 'openaccess' }, + { name: 'lease', endDate: dateToISOFormat('2019-01-16T00:00:00Z') }, + { name: 'embargo', startDate: dateToISOFormat('2019-01-16T00:00:00Z') }, + ]; + comp.saveBitstreamData(); + tick(); + + let path = 'metadata/dc.title'; + expect(operationsBuilder.add).toHaveBeenCalledWith( + pathCombiner.getPath(path), + mockFileFormData.metadata['dc.title'], + true + ); + + path = 'metadata/dc.description'; + expect(operationsBuilder.add).toHaveBeenCalledWith( + pathCombiner.getPath(path), + mockFileFormData.metadata['dc.description'], + true + ); + + path = 'accessConditions'; + expect(operationsBuilder.add).toHaveBeenCalledWith( + pathCombiner.getPath(path), + accessConditionsToSave, + true + ); + + // expect(comp.switchMode).toHaveBeenCalled(); + expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]); + + })); + + it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { + compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); + compAsAny.fileEditComp.formRef = {formGroup: null}; + compAsAny.pathCombiner = pathCombiner; + // const event = new Event('click', null); + // spyOn(comp, 'switchMode'); + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(of(false)); + + // expect(comp.switchMode).not.toHaveBeenCalled(); + expect(uploadService.updateFileData).not.toHaveBeenCalled(); + + })); + }); }); 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 eb8fbf95d0..32b82bec45 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,4 +1,4 @@ -import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; import { FormControl } from '@angular/forms'; import { @@ -60,6 +60,97 @@ import { Subscription } from 'rxjs'; }) export class SubmissionSectionUploadFileEditComponent implements OnInit { + /** + * The FormComponent reference + */ + @ViewChild('formRef') public formRef: FormComponent; + + /** + * The list of available access condition + * @type {Array} + */ + public availableAccessConditionOptions: any[]; + + /** + * The submission id + * @type {string} + */ + public 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} + */ + public collectionPolicyType: number; + + /** + * The configuration for the bitstream's metadata form + * @type {SubmissionFormsModel} + */ + public configMetadataForm: SubmissionFormsModel; + + /** + * The bitstream's metadata data + * @type {WorkspaceitemSectionUploadFileObject} + */ + public fileData: WorkspaceitemSectionUploadFileObject; + + /** + * The bitstream id + * @type {string} + */ + public fileId: string; + + /** + * The bitstream array key + * @type {string} + */ + public fileIndex: string; + + /** + * The form id + * @type {string} + */ + public formId: string; + + /** + * The section id + * @type {string} + */ + public sectionId: string; + + /** + * The submission id + * @type {string} + */ + public submissionId: string; + + /** + * The list of all available metadata + */ + formMetadata: string[] = []; + + /** + * The form model + * @type {DynamicFormControlModel[]} + */ + formModel: DynamicFormControlModel[]; + + /** + * When `true` form controls are deactivated + */ + isSaving = false; + + /** + * The [JsonPatchOperationPathCombiner] object + * @type {JsonPatchOperationPathCombiner} + */ + protected pathCombiner: JsonPatchOperationPathCombiner; + + protected subscriptions: Subscription[] = []; + /** * Initialize instance variables * @@ -84,94 +175,6 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { ) { } - /** - * The list of available access condition - * @type {Array} - */ - @Input() availableAccessConditionOptions: any[]; - - /** - * 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 list of all available metadata - */ - @Input() formMetadata: string[] = []; - - /** - * The [JsonPatchOperationPathCombiner] object - * @type {JsonPatchOperationPathCombiner} - */ - @Input() pathCombiner: JsonPatchOperationPathCombiner; - - /** - * The FormComponent reference - */ - @ViewChild('formRef') public formRef: FormComponent; - - /** - * The form model - * @type {DynamicFormControlModel[]} - */ - formModel: DynamicFormControlModel[]; - - isSaving = false; - - protected subscriptions: Subscription[] = []; - /** * Initialize form model values * @@ -379,7 +382,7 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { /** * Save bitstream metadata */ - protected saveBitstreamData() { + saveBitstreamData() { // validate form this.formService.validateAllFormFields(this.formRef.formGroup); const saveBitstreamDataSubscription = this.formService.isValid(this.formId).pipe( diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index a8ab34d866..2fd7be96d0 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { BrowserModule, By } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; @@ -17,10 +17,8 @@ import { SubmissionJsonPatchOperationsService } from '../../../../core/submissio import { SubmissionSectionUploadFileComponent } from './section-upload-file.component'; import { SubmissionServiceStub } from '../../../../shared/testing/submission-service.stub'; import { - mockFileFormData, mockSubmissionCollectionId, mockSubmissionId, - mockSubmissionObject, mockUploadConfigResponse, mockUploadFiles } from '../../../../shared/mocks/submission.mock'; @@ -32,10 +30,8 @@ import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; import { POLICY_DEFAULT_WITH_LIST } from '../section-upload.component'; import { JsonPatchOperationPathCombiner } from '../../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { getMockSectionUploadService } from '../../../../shared/mocks/section-upload.service.mock'; -import { FormFieldMetadataValueObject } from '../../../../shared/form/builder/models/form-field-metadata-value.model'; import { SubmissionSectionUploadFileEditComponent } from './edit/section-upload-file-edit.component'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { dateToISOFormat } from '../../../../shared/date.util'; const configMetadataFormMock = { rows: [{ From 7c23e2ef8238aa210c93131e62db43af2fa127e2 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 24 Nov 2021 14:44:38 +0100 Subject: [PATCH 12/59] 85123: Fix tests --- src/app/app.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9dc2b5a5ab..8b706d1fb6 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -293,7 +293,7 @@ export class AppComponent implements OnInit, AfterViewInit { const headTagConfigs = themeConfig?.headTags; if (isEmpty(headTagConfigs)) { - const parentThemeName = themeConfig.extends; + const parentThemeName = themeConfig?.extends; if (isNotEmpty(parentThemeName)) { // inherit the head tags of the parent theme return this.createHeadTags(parentThemeName); From b6dc7af13ec3a6c1a17db29f8e2888a8e886b1f9 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 24 Nov 2021 15:13:50 +0100 Subject: [PATCH 13/59] 85123: Fix tests #2 --- src/app/app.component.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 8b706d1fb6..e614fd918b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -246,6 +246,10 @@ export class AppComponent implements OnInit, AfterViewInit { */ private setThemeCss(themeName: string): void { const head = this.document.getElementsByTagName('head')[0]; + if (isEmpty(head)) { + return; + } + // Array.from to ensure we end up with an array, not an HTMLCollection, which would be // automatically updated if we add nodes later const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css')); @@ -272,6 +276,9 @@ export class AppComponent implements OnInit, AfterViewInit { private setHeadTags(themeName: string): void { const head = this.document.getElementsByTagName('head')[0]; + if (isEmpty(head)) { + return; + } // clear head tags const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag')); From 787358d1b0dd996ba4702e65ef5cc6dbfb5697bf Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 24 Nov 2021 16:08:26 +0100 Subject: [PATCH 14/59] 85123: Fix tests - 3 --- src/app/app.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 3f2dc45ce7..937b71eb5a 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -171,7 +171,8 @@ describe('App component', () => { TestBed.configureTestingModule(getDefaultTestBedConf()); TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')}); document = TestBed.inject(DOCUMENT); - headSpy = jasmine.createSpyObj('head', ['appendChild']); + headSpy = jasmine.createSpyObj('head', ['appendChild', 'getElementsByClassName']); + headSpy.getElementsByClassName.and.returnValue([]); spyOn(document, 'getElementsByTagName').and.returnValue([headSpy]); fixture = TestBed.createComponent(AppComponent); comp = fixture.componentInstance; From 6752acbf128e611a9593679ca64aa52b5fe1d88f Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 24 Nov 2021 20:00:21 +0100 Subject: [PATCH 15/59] [CST-4884] Test --- ...section-upload-file-edit.component.spec.ts | 19 ++++++++----------- .../section-upload-file.component.spec.ts | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index 866277108f..aa03d37eb2 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -49,7 +49,7 @@ const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', { const formMetadataMock = ['dc.title', 'dc.description']; -fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { +describe('SubmissionSectionUploadFileEditComponent test suite', () => { let comp: SubmissionSectionUploadFileEditComponent; let compAsAny: any; @@ -98,6 +98,7 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { SubmissionSectionUploadFileEditComponent, NgbModal, NgbActiveModal, + FormComponent, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents().then(); @@ -157,6 +158,8 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { comp.fileId = fileId; comp.configMetadataForm = configMetadataForm; comp.formMetadata = formMetadataMock; + + formService.isValid.and.returnValue(of(true)); }); afterEach(() => { @@ -233,12 +236,9 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { }); it('should save Bitstream File data properly when form is valid', fakeAsync(() => { - compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); - compAsAny.fileEditComp.formRef = {formGroup: null}; + compAsAny.formRef = {formGroup: null}; compAsAny.fileData = fileData; compAsAny.pathCombiner = pathCombiner; - // const event = new Event('click', null); - // spyOn(comp, 'switchMode'); formService.validateAllFormFields.and.callFake(() => null); formService.isValid.and.returnValue(of(true)); formService.getFormData.and.returnValue(of(mockFileFormData)); @@ -283,21 +283,18 @@ fdescribe('SubmissionSectionUploadFileEditComponent test suite', () => { true ); - // expect(comp.switchMode).toHaveBeenCalled(); expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]); })); it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { - compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); - compAsAny.fileEditComp.formRef = {formGroup: null}; + compAsAny.formRef = {formGroup: null}; compAsAny.pathCombiner = pathCombiner; - // const event = new Event('click', null); - // spyOn(comp, 'switchMode'); formService.validateAllFormFields.and.callFake(() => null); formService.isValid.and.returnValue(of(false)); + comp.saveBitstreamData(); + tick(); - // expect(comp.switchMode).not.toHaveBeenCalled(); expect(uploadService.updateFileData).not.toHaveBeenCalled(); })); diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index 2fd7be96d0..50b245e481 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -44,7 +44,7 @@ const configMetadataFormMock = { }] }; -fdescribe('SubmissionSectionUploadFileComponent test suite', () => { +describe('SubmissionSectionUploadFileComponent test suite', () => { let comp: SubmissionSectionUploadFileComponent; let compAsAny: any; From 27bce0e5bb550c1d02d7023eb7ffaf72243ca7cf Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 24 Nov 2021 20:17:16 +0100 Subject: [PATCH 16/59] [CST-4879] After changing options in access-conditions.xml, "grant access" fields are displayed incorrectly --- .../upload/file/edit/section-upload-file-edit.component.ts | 6 ++++-- .../sections/upload/file/section-upload-file.component.ts | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) 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 32b82bec45..fda86dd629 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 @@ -235,7 +235,7 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { public setOptions(model: DynamicFormControlModel, control: FormControl) { let accessCondition: AccessConditionOption = null; this.availableAccessConditionOptions.filter((element) => element.name === control.value) - .forEach((element) => accessCondition = element); + .forEach((element) => accessCondition = element ); if (isNotEmpty(accessCondition)) { const showGroups: boolean = accessCondition.hasStartDate === true || accessCondition.hasEndDate === true; @@ -364,7 +364,9 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT); const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT); const accessConditionGroupConfig = Object.assign({}, BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG); - accessConditionGroupConfig.group = [type, startDate, endDate]; + accessConditionGroupConfig.group = [type]; + if (hasStart.length > 0) { accessConditionGroupConfig.group.push(startDate); } + if (hasEnd.length > 0) { accessConditionGroupConfig.group.push(endDate); } return [new DynamicFormGroupModel(accessConditionGroupConfig, BITSTREAM_ACCESS_CONDITION_GROUP_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 e152d2f651..0841e1ca3a 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 @@ -247,10 +247,6 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { activeModal.componentInstance.pathCombiner = this.pathCombiner; activeModal.componentInstance.submissionId = this.submissionId; - /*activeModal.componentInstance.saveBitstreamDataEvent.subscribe((res) => { - console.log(JSON.stringify(res)); - });*/ - } ngOnDestroy(): void { From 7218c450e6993a4b50f6e169a45e41532affe701 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 24 Nov 2021 23:39:59 +0100 Subject: [PATCH 17/59] [CST-4884] Code cleanup and test improvement --- .../file/section-upload-file.component.html | 2 +- .../file/section-upload-file.component.scss | 6 -- .../section-upload-file.component.spec.ts | 84 ++----------------- .../file/section-upload-file.component.ts | 8 -- 4 files changed, 10 insertions(+), 90 deletions(-) diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 8749c8dcf2..1bfc52529b 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -8,7 +8,7 @@

{{fileName}} ({{fileData?.sizeBytes | dsFileSize}})

-
+
diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.scss b/src/app/submission/sections/upload/file/section-upload-file.component.scss index 256775eb66..e69de29bb2 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.scss +++ b/src/app/submission/sections/upload/file/section-upload-file.component.scss @@ -1,6 +0,0 @@ -.sticky-buttons { - position: sticky; - top: calc(var(--bs-dropdown-item-padding-x) * 3); - z-index: var(--ds-submission-footer-z-index); - background-color: rgba(255, 255, 255, .97); -} diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index 50b245e481..4fea8d3f25 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -217,86 +217,20 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { pathCombiner.subRootElement); }); - /*it('should save Bitstream File data properly when form is valid', fakeAsync(() => { - compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); - compAsAny.fileEditComp.formRef = {formGroup: null}; - compAsAny.fileData = fileData; - compAsAny.pathCombiner = pathCombiner; - const event = new Event('click', null); - spyOn(comp, 'switchMode'); - formService.validateAllFormFields.and.callFake(() => null); - formService.isValid.and.returnValue(observableOf(true)); - formService.getFormData.and.returnValue(observableOf(mockFileFormData)); + it('should open edit modal when edit button is clicked', () => { + spyOn(compAsAny, 'editBitstreamData').and.callThrough(); + comp.fileData = fileData; - const response = [ - Object.assign(mockSubmissionObject, { - sections: { - upload: { - files: mockUploadFiles - } - } - }) - ]; - operationsService.jsonPatchByResourceID.and.returnValue(observableOf(response)); + fixture.detectChanges(); - const accessConditionsToSave = [ - { name: 'openaccess' }, - { name: 'lease', endDate: dateToISOFormat('2019-01-16T00:00:00Z') }, - { name: 'embargo', startDate: dateToISOFormat('2019-01-16T00:00:00Z') }, - ]; - comp.saveBitstreamData(event); - tick(); + const modalBtn = fixture.debugElement.query(By.css('.fa-edit ')); - let path = 'metadata/dc.title'; - expect(operationsBuilder.add).toHaveBeenCalledWith( - pathCombiner.getPath(path), - mockFileFormData.metadata['dc.title'], - true - ); + modalBtn.nativeElement.click(); + fixture.detectChanges(); - path = 'metadata/dc.description'; - expect(operationsBuilder.add).toHaveBeenCalledWith( - pathCombiner.getPath(path), - mockFileFormData.metadata['dc.description'], - true - ); - - path = 'accessConditions'; - expect(operationsBuilder.add).toHaveBeenCalledWith( - pathCombiner.getPath(path), - accessConditionsToSave, - true - ); - - expect(comp.switchMode).toHaveBeenCalled(); - expect(uploadService.updateFileData).toHaveBeenCalledWith(submissionId, sectionId, mockUploadFiles[0].uuid, mockUploadFiles[0]); - - }));*/ - - /*it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { - compAsAny.fileEditComp = TestBed.inject(SubmissionSectionUploadFileEditComponent); - compAsAny.fileEditComp.formRef = {formGroup: null}; - compAsAny.pathCombiner = pathCombiner; - const event = new Event('click', null); - spyOn(comp, 'switchMode'); - formService.validateAllFormFields.and.callFake(() => null); - formService.isValid.and.returnValue(observableOf(false)); - - expect(comp.switchMode).not.toHaveBeenCalled(); - expect(uploadService.updateFileData).not.toHaveBeenCalled(); - - }));*/ - - it('should switch read mode', () => { - comp.readMode = false; - - comp.switchMode(); - expect(comp.readMode).toBeTruthy(); - - comp.switchMode(); - - expect(comp.readMode).toBeFalsy(); + expect(compAsAny.editBitstreamData).toHaveBeenCalled(); }); + }); }); 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 0841e1ca3a..53358d48e2 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 @@ -217,14 +217,6 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { }); } - /** - * Switch from edit form to metadata view - */ - public switchMode() { - this.readMode = !this.readMode; - this.cdr.detectChanges(); - } - editBitstreamData() { const options: NgbModalOptions = { From 47ed6bedb4e2f92bf78d38c924debfde7f4d2f4b Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 2 Dec 2021 12:47:38 +0100 Subject: [PATCH 18/59] 85123: BUGFIX: Use this.document instead of document Typo caused ReferenceError in SSR --- src/app/app.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e614fd918b..1d7e86ff80 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -287,7 +287,7 @@ export class AppComponent implements OnInit, AfterViewInit { } // create new head tags (not yet added to DOM) - const headTagFragment = document.createDocumentFragment(); + const headTagFragment = this.document.createDocumentFragment(); this.createHeadTags(themeName) .forEach(newHeadTag => headTagFragment.appendChild(newHeadTag)); From 03fd57e426e3575adec2790641b3b2b904294c86 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Thu, 2 Dec 2021 14:25:16 +0100 Subject: [PATCH 19/59] 85194: Fix favicon switching in production mode --- src/app/app.component.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 1d7e86ff80..03075f4f18 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -36,7 +36,7 @@ import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; import { environment } from '../environments/environment'; import { models } from './core/core.module'; import { LocaleService } from './core/locale/locale.service'; -import { hasValue, isEmpty, isNotEmpty } from './shared/empty.util'; +import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util'; import { KlaroService } from './shared/cookies/klaro.service'; import { GoogleAnalyticsService } from './statistics/google-analytics.service'; import { DOCUMENT, isPlatformBrowser } from '@angular/common'; @@ -246,7 +246,7 @@ export class AppComponent implements OnInit, AfterViewInit { */ private setThemeCss(themeName: string): void { const head = this.document.getElementsByTagName('head')[0]; - if (isEmpty(head)) { + if (hasNoValue(head)) { return; } @@ -276,13 +276,13 @@ export class AppComponent implements OnInit, AfterViewInit { private setHeadTags(themeName: string): void { const head = this.document.getElementsByTagName('head')[0]; - if (isEmpty(head)) { + if (hasNoValue(head)) { return; } // clear head tags const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag')); - if (isNotEmpty(currentHeadTags)) { + if (hasValue(currentHeadTags)) { currentHeadTags.forEach((currentHeadTag: any) => currentHeadTag.remove()); } @@ -299,16 +299,16 @@ export class AppComponent implements OnInit, AfterViewInit { const themeConfig = this.themeService.getThemeConfigFor(themeName); const headTagConfigs = themeConfig?.headTags; - if (isEmpty(headTagConfigs)) { + if (hasNoValue(headTagConfigs)) { const parentThemeName = themeConfig?.extends; - if (isNotEmpty(parentThemeName)) { + if (hasValue(parentThemeName)) { // inherit the head tags of the parent theme return this.createHeadTags(parentThemeName); } const defaultThemeName = DEFAULT_THEME_CONFIG.name; if ( - isEmpty(defaultThemeName) || + hasNoValue(defaultThemeName) || themeName === defaultThemeName || themeName === BASE_THEME_NAME ) { @@ -335,7 +335,7 @@ export class AppComponent implements OnInit, AfterViewInit { private createHeadTag(headTagConfig: HeadTagConfig): HTMLElement { const tag = this.document.createElement(headTagConfig.tagName); - if (isNotEmpty(headTagConfig.attributes)) { + if (hasValue(headTagConfig.attributes)) { Object.entries(headTagConfig.attributes) .forEach(([key, value]) => tag.setAttribute(key, value)); } From c1555326fa3a4d676841125060d0a803cfdd9cb1 Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 3 Dec 2021 21:17:36 -0600 Subject: [PATCH 20/59] build app config --- config/.gitignore | 1 + config/appConfig.json | 14 + scripts/set-env.ts | 8 +- .../notification.component.spec.ts | 12 - .../notifications.service.spec.ts | 13 - src/assets/.gitignore | 1 + ...g.interface.ts => app-config.interface.ts} | 10 +- src/config/config.ts | 151 +++++++++ src/config/default-app-config.ts | 306 ++++++++++++++++++ src/environments/environment.common.ts | 4 +- src/environments/mock-environment.ts | 4 +- src/main.ts | 20 -- webpack/webpack.browser.ts | 9 +- 13 files changed, 498 insertions(+), 55 deletions(-) create mode 100644 config/.gitignore create mode 100644 config/appConfig.json create mode 100644 src/assets/.gitignore rename src/config/{global-config.interface.ts => app-config.interface.ts} (87%) create mode 100644 src/config/config.ts create mode 100644 src/config/default-app-config.ts delete mode 100644 src/main.ts diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000000..e1899191e1 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1 @@ +appConfig.*.json diff --git a/config/appConfig.json b/config/appConfig.json new file mode 100644 index 0000000000..3390bbb478 --- /dev/null +++ b/config/appConfig.json @@ -0,0 +1,14 @@ +{ + "ui": { + "ssl": false, + "host": "localhost", + "port": 4000, + "nameSpace": "/" + }, + "rest": { + "ssl": true, + "host": "api7.dspace.org", + "port": 443, + "nameSpace": "/server" + } +} diff --git a/scripts/set-env.ts b/scripts/set-env.ts index b3516ae68f..fd806ee1d9 100644 --- a/scripts/set-env.ts +++ b/scripts/set-env.ts @@ -1,6 +1,6 @@ import { writeFile } from 'fs'; import { environment as commonEnv } from '../src/environments/environment.common'; -import { GlobalConfig } from '../src/config/global-config.interface'; +import { AppConfig } from '../src/config/app-config.interface'; import { ServerConfig } from '../src/config/server-config.interface'; import { hasValue } from '../src/app/shared/empty.util'; @@ -42,7 +42,7 @@ const processEnv = { process.env.DSPACE_REST_PORT, process.env.DSPACE_REST_NAMESPACE, process.env.DSPACE_REST_SSL) -} as GlobalConfig; +} as AppConfig; import(environmentFilePath) .then((file) => generateEnvironmentFile(merge.all([commonEnv, file.environment, processEnv], mergeOptions))) @@ -51,7 +51,7 @@ import(environmentFilePath) generateEnvironmentFile(merge(commonEnv, processEnv, mergeOptions)) }); -function generateEnvironmentFile(file: GlobalConfig): void { +function generateEnvironmentFile(file: AppConfig): void { file.production = production; buildBaseUrls(file); const contents = `export const environment = ` + JSON.stringify(file); @@ -86,7 +86,7 @@ function createServerConfig(host?: string, port?: string, nameSpace?: string, ss return result; } -function buildBaseUrls(config: GlobalConfig): void { +function buildBaseUrls(config: AppConfig): void { for (const key in config) { if (config.hasOwnProperty(key) && config[key].host) { config[key].baseUrl = [ diff --git a/src/app/shared/notifications/notification/notification.component.spec.ts b/src/app/shared/notifications/notification/notification.component.spec.ts index 2bded57636..1d19464578 100644 --- a/src/app/shared/notifications/notification/notification.component.spec.ts +++ b/src/app/shared/notifications/notification/notification.component.spec.ts @@ -10,8 +10,6 @@ import { NotificationsService } from '../notifications.service'; import { NotificationType } from '../models/notification-type'; import { notificationsReducer } from '../notifications.reducers'; import { NotificationOptions } from '../models/notification-options.model'; -import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces'; -import { GlobalConfig } from '../../../../config/global-config.interface'; import { Notification } from '../models/notification.model'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; @@ -33,16 +31,6 @@ describe('NotificationComponent', () => { /* tslint:disable:no-empty */ notifications: [] }); - const envConfig: GlobalConfig = { - notifications: { - rtl: false, - position: ['top', 'right'], - maxStack: 8, - timeOut: 5000, - clickToClose: true, - animate: 'scale' - } as INotificationBoardOptions, - } as any; TestBed.configureTestingModule({ imports: [ diff --git a/src/app/shared/notifications/notifications.service.spec.ts b/src/app/shared/notifications/notifications.service.spec.ts index bb0f94eede..92c74e0017 100644 --- a/src/app/shared/notifications/notifications.service.spec.ts +++ b/src/app/shared/notifications/notifications.service.spec.ts @@ -8,7 +8,6 @@ import { of as observableOf } from 'rxjs'; import { NewNotificationAction, RemoveAllNotificationsAction, RemoveNotificationAction } from './notifications.actions'; import { Notification } from './models/notification.model'; import { NotificationType } from './models/notification-type'; -import { GlobalConfig } from '../../../config/global-config.interface'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; import { storeModuleConfig } from '../../app.reducer'; @@ -19,18 +18,6 @@ describe('NotificationsService test', () => { select: observableOf(true) }); let service: NotificationsService; - let envConfig: GlobalConfig; - - envConfig = { - notifications: { - rtl: false, - position: ['top', 'right'], - maxStack: 8, - timeOut: 5000, - clickToClose: true, - animate: 'scale' - }, - } as any; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ diff --git a/src/assets/.gitignore b/src/assets/.gitignore new file mode 100644 index 0000000000..c25cf8c104 --- /dev/null +++ b/src/assets/.gitignore @@ -0,0 +1 @@ +appConfig.json diff --git a/src/config/global-config.interface.ts b/src/config/app-config.interface.ts similarity index 87% rename from src/config/global-config.interface.ts rename to src/config/app-config.interface.ts index d46822eb61..9a993cb415 100644 --- a/src/config/global-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -1,3 +1,4 @@ +import { InjectionToken } from '@angular/core'; import { Config } from './config.interface'; import { ServerConfig } from './server-config.interface'; import { CacheConfig } from './cache-config.interface'; @@ -14,7 +15,7 @@ import { AuthConfig } from './auth-config.interfaces'; import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface'; -export interface GlobalConfig extends Config { +interface AppConfig extends Config { ui: UIServerConfig; rest: ServerConfig; production: boolean; @@ -33,3 +34,10 @@ export interface GlobalConfig extends Config { themes: ThemeConfig[]; mediaViewer: MediaViewerConfig; } + +const APP_CONFIG = new InjectionToken('APP_CONFIG'); + +export { + AppConfig, + APP_CONFIG +}; diff --git a/src/config/config.ts b/src/config/config.ts new file mode 100644 index 0000000000..d695c141f8 --- /dev/null +++ b/src/config/config.ts @@ -0,0 +1,151 @@ +import * as colors from 'colors'; +import * as fs from 'fs'; +import { join } from 'path'; +import { AppConfig } from './app-config.interface'; +import { Config } from './config.interface'; +import { DefaultAppConfig } from './default-app-config'; +import { ServerConfig } from './server-config.interface'; + +const CONFIG_PATH = join(process.cwd(), 'config'); + +const APP_CONFIG_PATH = join(CONFIG_PATH, 'appConfig.json'); + +type Environment = 'production' | 'development' | 'test'; + +const getEnvironment = (): Environment => { + let environment: Environment = 'development'; + if (!!process.env.NODE_ENV) { + switch (process.env.NODE_ENV) { + case 'prod': + case 'production': + environment = 'production'; + break; + case 'test': + environment = 'test'; + break; + case 'dev': + case 'development': + break; + default: + console.warn(`Unknown NODE_ENV ${process.env.NODE_ENV}. Defaulting to development`); + } + } + + return environment; +}; + +const getDistConfigPath = (env: Environment) => { + // determine app config filename variations + let envVariations; + switch (env) { + case 'production': + envVariations = ['prod', 'production']; + break; + case 'test': + envVariations = ['test']; + break; + case 'development': + default: + envVariations = ['dev', 'development'] + } + + // check if any environment variations of app config exist + for (const envVariation of envVariations) { + const altDistConfigPath = join(CONFIG_PATH, `appConfig.${envVariation}.json`); + if (fs.existsSync(altDistConfigPath)) { + return altDistConfigPath; + } + } + + // return default config/appConfig.json + return APP_CONFIG_PATH; +}; + +const overrideWithConfig = (config: Config, pathToConfig: string) => { + try { + console.log(`Overriding app config with ${pathToConfig}`); + const externalConfig = fs.readFileSync(pathToConfig, 'utf8'); + Object.assign(config, JSON.parse(externalConfig)); + } catch (err) { + console.error(err); + } +}; + +const overrideWithEnvironment = (config: Config, key: string = '') => { + for (const property in config) { + const variable = `${key}${!!key ? '_' : ''}${property.toUpperCase()}`; + const innerConfig = config[property]; + if (!!innerConfig) { + if (typeof innerConfig === 'object') { + overrideWithEnvironment(innerConfig, variable); + } else { + if (!!process.env[variable]) { + console.log(`Applying environment variable ${variable} with value ${process.env[variable]}`); + config[property] = process.env[variable]; + } + } + } + } +}; + +const buildBaseUrl = (config: ServerConfig): void => { + config.baseUrl = [ + config.ssl ? 'https://' : 'http://', + config.host, + config.port && config.port !== 80 && config.port !== 443 ? `:${config.port}` : '', + config.nameSpace && config.nameSpace.startsWith('/') ? config.nameSpace : `/${config.nameSpace}` + ].join(''); +}; + +export const buildAppConfig = (destConfigPath: string): AppConfig => { + // start with default app config + const appConfig: AppConfig = new DefaultAppConfig(); + + // determine which dist app config by environment + const env = getEnvironment(); + + switch (env) { + case 'production': + console.log(`Building ${colors.red.bold(`production`)} app config`); + break; + case 'test': + console.log(`Building ${colors.blue.bold(`test`)} app config`); + break; + default: + console.log(`Building ${colors.green.bold(`development`)} app config`); + } + + // override with dist config + const distConfigPath = getDistConfigPath(env); + if (fs.existsSync(distConfigPath)) { + overrideWithConfig(appConfig, distConfigPath); + } else { + console.warn(`Unable to find dist config file at ${distConfigPath}`); + } + + // override with external config if specified by environment variable `APP_CONFIG_PATH` + const externalConfigPath = process.env.APP_CONFIG_PATH; + if (!!externalConfigPath) { + if (fs.existsSync(externalConfigPath)) { + overrideWithConfig(appConfig, externalConfigPath); + } else { + console.warn(`Unable to find external config file at ${externalConfigPath}`); + } + } + + // override with environment variables + overrideWithEnvironment(appConfig); + + // apply build defined production + appConfig.production = env === 'production'; + + // build base URLs + buildBaseUrl(appConfig.ui); + buildBaseUrl(appConfig.rest); + + fs.writeFileSync(destConfigPath, JSON.stringify(appConfig, null, 2)); + + console.log(`Angular ${colors.bold('appConfig.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); + + return appConfig; +} diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts new file mode 100644 index 0000000000..12859db977 --- /dev/null +++ b/src/config/default-app-config.ts @@ -0,0 +1,306 @@ +import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { RestRequestMethod } from '../app/core/data/rest-request-method'; +import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; +import { AppConfig } from './app-config.interface'; +import { AuthConfig } from './auth-config.interfaces'; +import { BrowseByConfig } from './browse-by-config.interface'; +import { CacheConfig } from './cache-config.interface'; +import { CollectionPageConfig } from './collection-page-config.interface'; +import { FormConfig } from './form-config.interfaces'; +import { ItemPageConfig } from './item-page-config.interface'; +import { LangConfig } from './lang-config.interface'; +import { MediaViewerConfig } from './media-viewer-config.interface'; +import { INotificationBoardOptions } from './notifications-config.interfaces'; +import { ServerConfig } from './server-config.interface'; +import { SubmissionConfig } from './submission-config.interface'; +import { ThemeConfig } from './theme.model'; +import { UIServerConfig } from './ui-server-config.interface'; +import { UniversalConfig } from './universal-config.interface'; + +export class DefaultAppConfig implements AppConfig { + production: boolean = false; + + // Angular Universal server settings. + // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. + ui: UIServerConfig = { + ssl: false, + host: 'localhost', + port: 4000, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/', + + // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). + rateLimiter: { + windowMs: 1 * 60 * 1000, // 1 minute + max: 500 // limit each IP to 500 requests per windowMs + } + }; + + // The REST API server settings. + // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. + rest: ServerConfig = { + ssl: false, + host: 'localhost', + port: 8080, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/', + }; + + // Caching settings + cache: CacheConfig = { + // NOTE: how long should objects be cached for by default + msToLive: { + default: 15 * 60 * 1000 // 15 minutes + }, + control: 'max-age=60', // revalidate browser + autoSync: { + defaultTime: 0, + maxBufferSize: 100, + timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds + } + }; + + // Authentication settings + auth: AuthConfig = { + // Authentication UI settings + ui: { + // the amount of time before the idle warning is shown + timeUntilIdle: 15 * 60 * 1000, // 15 minutes + // the amount of time the user has to react after the idle warning is shown before they are logged out. + idleGracePeriod: 5 * 60 * 1000 // 5 minutes + }, + // Authentication REST settings + rest: { + // If the rest token expires in less than this amount of time, it will be refreshed automatically. + // This is independent from the idle warning. + timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes + } + }; + + // Form settings + form: FormConfig = { + // NOTE: Map server-side validators to comparative Angular form validators + validatorMap: { + required: 'required', + regex: 'pattern' + } + }; + + // Notifications + notifications: INotificationBoardOptions = { + rtl: false, + position: ['top', 'right'], + maxStack: 8, + // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically + timeOut: 5000, // 5 second + clickToClose: true, + // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' + animate: NotificationAnimationsType.Scale + }; + + // Submission settings + submission: SubmissionConfig = { + autosave: { + // NOTE: which metadata trigger an autosave + metadata: [], + /** + * NOTE: after how many time (milliseconds) submission is saved automatically + * eg. timer: 5 * (1000 * 60); // 5 minutes + */ + timer: 0 + }, + icons: { + metadata: [ + /** + * NOTE: example of configuration + * { + * // NOTE: metadata name + * name: 'dc.author', + * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + * style: 'fa-user' + * } + */ + { + name: 'dc.author', + style: 'fas fa-user' + }, + // default configuration + { + name: 'default', + style: '' + } + ], + authority: { + confidence: [ + /** + * NOTE: example of configuration + * { + * // NOTE: confidence value + * value: 'dc.author', + * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used + * style: 'fa-user' + * } + */ + { + value: 600, + style: 'text-success' + }, + { + value: 500, + style: 'text-info' + }, + { + value: 400, + style: 'text-warning' + }, + // default configuration + { + value: 'default', + style: 'text-muted' + } + + ] + } + } + }; + + // Angular Universal settings + universal: UniversalConfig = { + preboot: true, + async: true, + time: false + }; + + // NOTE: will log all redux actions and transfers in console + debug: boolean = false; + + // Default Language in which the UI will be rendered if the user's browser language is not an active language + defaultLanguage: string = 'en'; + + // Languages. DSpace Angular holds a message catalog for each of the following languages. + // When set to active, users will be able to switch to the use of this language in the user interface. + languages: LangConfig[] = [ + { code: 'en', label: 'English', active: true }, + { code: 'cs', label: 'Čeština', active: true }, + { code: 'de', label: 'Deutsch', active: true }, + { code: 'es', label: 'Español', active: true }, + { code: 'fr', label: 'Français', active: true }, + { code: 'lv', label: 'Latviešu', active: true }, + { code: 'hu', label: 'Magyar', active: true }, + { code: 'nl', label: 'Nederlands', active: true }, + { code: 'pt-PT', label: 'Português', active: true }, + { code: 'pt-BR', label: 'Português do Brasil', active: true }, + { code: 'fi', label: 'Suomi', active: true } + ]; + + // Browse-By Pages + browseBy: BrowseByConfig = { + // Amount of years to display using jumps of one year (current year - oneYearLimit) + oneYearLimit: 10, + // Limit for years to display using jumps of five years (current year - fiveYearLimit) + fiveYearLimit: 30, + // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) + defaultLowerLimit: 1900, + // List of all the active Browse-By types + // Adding a type will activate their Browse-By page and add them to the global navigation menu, + // as well as community and collection pages + // Allowed fields and their purpose: + // id: The browse id to use for fetching info from the rest api + // type: The type of Browse-By page to display + // metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') + types: [ + { + id: 'title', + type: BrowseByType.Title, + }, + { + id: 'dateissued', + type: BrowseByType.Date, + metadataField: 'dc.date.issued' + }, + { + id: 'author', + type: BrowseByType.Metadata + }, + { + id: 'subject', + type: BrowseByType.Metadata + } + ] + }; + + // Item Page Config + item: ItemPageConfig = { + edit: { + undoTimeout: 10000 // 10 seconds + } + }; + + // Collection Page Config + collection: CollectionPageConfig = { + edit: { + undoTimeout: 10000 // 10 seconds + } + }; + + // Theme Config + themes: ThemeConfig[] = [ + // Add additional themes here. In the case where multiple themes match a route, the first one + // in this list will get priority. It is advisable to always have a theme that matches + // every route as the last one + + // { + // // A theme with a handle property will match the community, collection or item with the given + // // handle, and all collections and/or items within it + // name: 'custom', + // handle: '10673/1233' + // }, + // { + // // A theme with a regex property will match the route using a regular expression. If it + // // matches the route for a community or collection it will also apply to all collections + // // and/or items within it + // name: 'custom', + // regex: 'collections\/e8043bc2.*' + // }, + // { + // // A theme with a uuid property will match the community, collection or item with the given + // // ID, and all collections and/or items within it + // name: 'custom', + // uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' + // }, + // { + // // The extends property specifies an ancestor theme (by name). Whenever a themed component is not found + // // in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. + // name: 'custom-A', + // extends: 'custom-B', + // // Any of the matching properties above can be used + // handle: '10673/34' + // }, + // { + // name: 'custom-B', + // extends: 'custom', + // handle: '10673/12' + // }, + // { + // // A theme with only a name will match every route + // name: 'custom' + // }, + // { + // // This theme will use the default bootstrap styling for DSpace components + // name: BASE_THEME_NAME + // }, + + { + // The default dspace theme + name: 'dspace' + } + ]; + + // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). + // For images, this enables a gallery viewer where you can zoom or page through images. + // For videos, this enables embedded video streaming + mediaViewer: MediaViewerConfig = { + image: false, + video: false + }; +} diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index b1cbd699a3..6bb2c8dc51 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -1,9 +1,9 @@ -import { GlobalConfig } from '../config/global-config.interface'; +import { AppConfig } from '../config/app-config.interface'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; import { RestRequestMethod } from '../app/core/data/rest-request-method'; -export const environment: GlobalConfig = { +export const environment: AppConfig = { production: true, // Angular Universal server settings. // NOTE: these must be "synced" with the 'dspace.ui.url' setting in your backend's local.cfg. diff --git a/src/environments/mock-environment.ts b/src/environments/mock-environment.ts index 824c8c8a83..02c1012c68 100644 --- a/src/environments/mock-environment.ts +++ b/src/environments/mock-environment.ts @@ -2,9 +2,9 @@ import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; import { RestRequestMethod } from '../app/core/data/rest-request-method'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; -import { GlobalConfig } from '../config/global-config.interface'; +import { AppConfig } from '../config/app-config.interface'; -export const environment: Partial = { +export const environment: Partial = { rest: { ssl: true, host: 'rest.com', diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 734cd3c5f5..0000000000 --- a/src/main.ts +++ /dev/null @@ -1,20 +0,0 @@ -import 'core-js/es/reflect'; -import 'zone.js/dist/zone'; -import 'reflect-metadata'; - -import { enableProdMode } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; - -if (environment.production) { - enableProdMode(); -} - -document.addEventListener('DOMContentLoaded', () => { - document.addEventListener('DOMContentLoaded', () => { - platformBrowserDynamic().bootstrapModule(AppModule) - .catch((err) => console.error(err)); - }); -}); diff --git a/webpack/webpack.browser.ts b/webpack/webpack.browser.ts index bc281e9b43..3c1d01dda0 100644 --- a/webpack/webpack.browser.ts +++ b/webpack/webpack.browser.ts @@ -1,8 +1,15 @@ +import { buildAppConfig } from '../src/config/config'; import { commonExports } from './webpack.common'; +import { join } from 'path'; module.exports = Object.assign({}, commonExports, { target: 'web', node: { module: 'empty' - } + }, + devServer: { + before(app, server) { + buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); + } + } }); From 71f5b46639babc0ab551993dfcc9eda4f537c196 Mon Sep 17 00:00:00 2001 From: William Welling Date: Sat, 4 Dec 2021 10:37:56 -0600 Subject: [PATCH 21/59] use standard environments --- .github/workflows/build.yml | 2 +- .gitignore | 4 - angular.json | 50 +- config/appConfig.json | 6 - cypress/.gitignore | 2 + mock-nodemon.json | 5 - nodemon.json | 6 - package.json | 38 +- scripts/serve.ts | 13 +- scripts/set-env.ts | 116 ---- scripts/set-mock-env.ts | 11 - scripts/test-rest.ts | 8 +- server.ts | 11 +- src/app/app.component.ts | 15 +- src/app/app.module.ts | 18 +- .../community-authorizations.component.ts | 4 +- src/app/core/data/request.models.ts | 3 +- .../core/shared/hal-endpoint.service.spec.ts | 2 +- .../resource-policy-form.component.spec.ts | 4 +- src/app/shared/theme-support/theme.effects.ts | 15 +- src/config/{config.ts => config.server.ts} | 76 ++- src/config/config.util.ts | 37 ++ src/environments/environment.ci.ts | 294 ++++++++++ ...ironment.common.ts => environment.prod.ts} | 129 ++--- src/environments/environment.template.ts | 10 - ...ock-environment.ts => environment.test.ts} | 66 ++- src/environments/environment.ts | 309 ++++++++++ src/main.browser.ts | 27 +- webpack/webpack.browser.ts | 7 +- yarn.lock | 536 +----------------- 30 files changed, 963 insertions(+), 861 deletions(-) create mode 100644 cypress/.gitignore delete mode 100644 mock-nodemon.json delete mode 100644 nodemon.json delete mode 100644 scripts/set-env.ts delete mode 100644 scripts/set-mock-env.ts rename src/config/{config.ts => config.server.ts} (55%) create mode 100644 src/config/config.util.ts create mode 100644 src/environments/environment.ci.ts rename src/environments/{environment.common.ts => environment.prod.ts} (84%) delete mode 100644 src/environments/environment.template.ts rename src/environments/{mock-environment.ts => environment.test.ts} (90%) create mode 100644 src/environments/environment.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7758020724..ab537f80cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: run: yarn run lint - name: Run build - run: yarn run build:prod + run: yarn run build:ssr:ci - name: Run specs (unit tests) run: yarn run test:headless diff --git a/.gitignore b/.gitignore index f110ba720c..026110f222 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,6 @@ npm-debug.log /build/ -/src/environments/environment.ts -/src/environments/environment.dev.ts -/src/environments/environment.prod.ts - /coverage /dist/ diff --git a/angular.json b/angular.json index c6607fc80a..b81d166cea 100644 --- a/angular.json +++ b/angular.json @@ -68,6 +68,12 @@ }, "configurations": { "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], "optimization": true, "outputHashing": "all", "extractCss": true, @@ -88,6 +94,22 @@ "maximumError": "300kb" } ] + }, + "ci": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.ci.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true } } }, @@ -139,6 +161,16 @@ } ], "scripts": [] + }, + "configurations": { + "test": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.test.ts" + } + ] + } } }, "lint": { @@ -183,7 +215,23 @@ "configurations": { "production": { "sourceMap": false, - "optimization": true + "optimization": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ] + }, + "ci": { + "sourceMap": false, + "optimization": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.ci.ts" + } + ] } } }, diff --git a/config/appConfig.json b/config/appConfig.json index 3390bbb478..a70d8ea6ab 100644 --- a/config/appConfig.json +++ b/config/appConfig.json @@ -1,10 +1,4 @@ { - "ui": { - "ssl": false, - "host": "localhost", - "port": 4000, - "nameSpace": "/" - }, "rest": { "ssl": true, "host": "api7.dspace.org", diff --git a/cypress/.gitignore b/cypress/.gitignore new file mode 100644 index 0000000000..99bd2a6312 --- /dev/null +++ b/cypress/.gitignore @@ -0,0 +1,2 @@ +screenshots/ +videos/ diff --git a/mock-nodemon.json b/mock-nodemon.json deleted file mode 100644 index 18fc86bd9d..0000000000 --- a/mock-nodemon.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "watch": ["src/environments/mock-environment.ts"], - "ext": "ts", - "exec": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts" -} diff --git a/nodemon.json b/nodemon.json deleted file mode 100644 index 39e9d9aa5b..0000000000 --- a/nodemon.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "watch": ["src/environments"], - "ext": "ts", - "ignore": ["src/environments/environment.ts", "src/environments/mock-environment.ts"], - "exec": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --dev" -} diff --git a/package.json b/package.json index 236845da1a..4ce674ce1c 100644 --- a/package.json +++ b/package.json @@ -3,23 +3,10 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "config:dev": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --dev", - "config:prod": "ts-node --project ./tsconfig.ts-node.json scripts/set-env.ts --prod", - "config:test": "ts-node --project ./tsconfig.ts-node.json scripts/set-mock-env.ts", - "config:test:watch": "nodemon --config mock-nodemon.json", - "config:dev:watch": "nodemon", - "config:check:rest": "yarn run config:prod && ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts", - "config:dev:check:rest": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts", - "prestart:dev": "yarn run config:dev", - "prebuild": "yarn run config:dev", - "pretest": "yarn run config:test", - "pretest:watch": "yarn run config:test", - "pretest:headless": "yarn run config:test", - "prebuild:prod": "yarn run config:prod", - "pree2e": "yarn run config:prod", + "test:rest": "ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts", "start": "yarn run start:prod", "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts", - "start:dev": "npm-run-all --parallel config:dev:watch serve", + "start:dev": "yarn run serve", "start:prod": "yarn run build:prod && yarn run serve:ssr", "start:mirador:prod": "yarn run build:mirador && yarn run start:prod", "analyze": "webpack-bundle-analyzer dist/browser/stats.json", @@ -27,27 +14,25 @@ "build:stats": "ng build --stats-json", "build:prod": "yarn run build:ssr", "build:ssr": "ng build --configuration production && ng run dspace-angular:server:production", - "test:watch": "npm-run-all --parallel config:test:watch test", - "test": "ng test --sourceMap=true --watch=true", - "test:headless": "ng test --watch=false --sourceMap=true --browsers=ChromeHeadless --code-coverage", + "build:ssr:ci": "ng build --configuration ci && ng run dspace-angular:server:ci", + "test": "ng test --sourceMap=true --watch=true --configuration test", + "test:headless": "ng test --sourceMap=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage", "lint": "ng lint", "lint-fix": "ng lint --fix=true", "e2e": "ng e2e", "serve:ssr": "node dist/server/main", + "clean:dev:config": "rimraf src/assets/appConfig.json", "clean:coverage": "rimraf coverage", "clean:dist": "rimraf dist", "clean:doc": "rimraf doc", "clean:log": "rimraf *.log*", "clean:json": "rimraf *.records.json", - "clean:bld": "rimraf build", "clean:node": "rimraf node_modules", - "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json && yarn run clean:bld", - "clean": "yarn run clean:prod && yarn run clean:env && yarn run clean:node", - "clean:env": "rimraf src/environments/environment.ts", - "sync-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts", + "clean:prod": "yarn run clean:dist && yarn run clean:log && yarn run clean:doc && yarn run clean:coverage && yarn run clean:json", + "clean": "yarn run clean:prod && yarn run clean:node && yarn run clean:dev:config", + "sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts", "build:mirador": "webpack --config webpack/webpack.mirador.config.ts", - "merge-i18n": "yarn run config:dev && ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts", - "postinstall": "ngcc", + "merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts", "cypress:open": "cypress open", "cypress:run": "cypress run" }, @@ -74,7 +59,6 @@ "@angular/platform-browser-dynamic": "~11.2.14", "@angular/platform-server": "~11.2.14", "@angular/router": "~11.2.14", - "@angularclass/bootloader": "1.0.1", "@kolkov/ngx-gallery": "^1.2.3", "@ng-bootstrap/ng-bootstrap": "9.1.3", "@ng-dynamic-forms/core": "^13.0.0", @@ -178,8 +162,6 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "karma-mocha-reporter": "2.2.5", - "nodemon": "^2.0.2", - "npm-run-all": "^4.1.5", "optimize-css-assets-webpack-plugin": "^5.0.4", "postcss-apply": "0.11.0", "postcss-import": "^12.0.1", diff --git a/scripts/serve.ts b/scripts/serve.ts index c69f8e8a21..903374c615 100644 --- a/scripts/serve.ts +++ b/scripts/serve.ts @@ -1,11 +1,16 @@ -import { environment } from '../src/environments/environment'; - import * as child from 'child_process'; +import { environment } from '../src/environments/environment'; + +// import { AppConfig } from '../src/config/app-config.interface'; +// import { buildAppConfig } from '../src/config/config.server'; + +// const appConfig: AppConfig = buildAppConfig(); + /** - * Calls `ng serve` with the following arguments configured for the UI in the environment file: host, port, nameSpace, ssl + * Calls `ng serve` with the following arguments configured for the UI in the environment: host, port, nameSpace, ssl */ child.spawn( - `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --servePath ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`, + `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --serve-path ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`, { stdio:'inherit', shell: true } ); diff --git a/scripts/set-env.ts b/scripts/set-env.ts deleted file mode 100644 index fd806ee1d9..0000000000 --- a/scripts/set-env.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { writeFile } from 'fs'; -import { environment as commonEnv } from '../src/environments/environment.common'; -import { AppConfig } from '../src/config/app-config.interface'; -import { ServerConfig } from '../src/config/server-config.interface'; -import { hasValue } from '../src/app/shared/empty.util'; - -// Configure Angular `environment.ts` file path -const targetPath = './src/environments/environment.ts'; -// Load node modules -const colors = require('colors'); -require('dotenv').config(); -const merge = require('deepmerge'); -const mergeOptions = { arrayMerge: (destinationArray, sourceArray, options) => sourceArray }; -const environment = process.argv[2]; -let environmentFilePath; -let production = false; - -switch (environment) { - case '--prod': - case '--production': - production = true; - console.log(`Building ${colors.red.bold(`production`)} environment`); - environmentFilePath = '../src/environments/environment.prod.ts'; - break; - case '--test': - console.log(`Building ${colors.blue.bold(`test`)} environment`); - environmentFilePath = '../src/environments/environment.test.ts'; - break; - default: - console.log(`Building ${colors.green.bold(`development`)} environment`); - environmentFilePath = '../src/environments/environment.dev.ts'; -} - -const processEnv = { - ui: createServerConfig( - process.env.DSPACE_HOST, - process.env.DSPACE_PORT, - process.env.DSPACE_NAMESPACE, - process.env.DSPACE_SSL), - rest: createServerConfig( - process.env.DSPACE_REST_HOST, - process.env.DSPACE_REST_PORT, - process.env.DSPACE_REST_NAMESPACE, - process.env.DSPACE_REST_SSL) -} as AppConfig; - -import(environmentFilePath) - .then((file) => generateEnvironmentFile(merge.all([commonEnv, file.environment, processEnv], mergeOptions))) - .catch(() => { - console.log(colors.yellow.bold(`No specific environment file found for ` + environment)); - generateEnvironmentFile(merge(commonEnv, processEnv, mergeOptions)) - }); - -function generateEnvironmentFile(file: AppConfig): void { - file.production = production; - buildBaseUrls(file); - const contents = `export const environment = ` + JSON.stringify(file); - writeFile(targetPath, contents, (err) => { - if (err) { - throw console.error(err); - } else { - console.log(`Angular ${colors.bold('environment.ts')} file generated correctly at ${colors.bold(targetPath)} \n`); - } - }); -} - -// allow to override a few important options by environment variables -function createServerConfig(host?: string, port?: string, nameSpace?: string, ssl?: string): ServerConfig { - const result = {} as any; - if (hasValue(host)) { - result.host = host; - } - - if (hasValue(nameSpace)) { - result.nameSpace = nameSpace; - } - - if (hasValue(port)) { - result.port = Number(port); - } - - if (hasValue(ssl)) { - result.ssl = ssl.trim().match(/^(true|1|yes)$/i) ? true : false; - } - - return result; -} - -function buildBaseUrls(config: AppConfig): void { - for (const key in config) { - if (config.hasOwnProperty(key) && config[key].host) { - config[key].baseUrl = [ - getProtocol(config[key].ssl), - getHost(config[key].host), - getPort(config[key].port), - getNameSpace(config[key].nameSpace) - ].join(''); - } - } -} - -function getProtocol(ssl: boolean): string { - return ssl ? 'https://' : 'http://'; -} - -function getHost(host: string): string { - return host; -} - -function getPort(port: number): string { - return port ? (port !== 80 && port !== 443) ? ':' + port : '' : ''; -} - -function getNameSpace(nameSpace: string): string { - return nameSpace ? nameSpace.charAt(0) === '/' ? nameSpace : '/' + nameSpace : ''; -} diff --git a/scripts/set-mock-env.ts b/scripts/set-mock-env.ts deleted file mode 100644 index 5271432896..0000000000 --- a/scripts/set-mock-env.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { copyFile } from 'fs'; - -// Configure Angular `environment.ts` file path -const sourcePath = './src/environments/mock-environment.ts'; -const targetPath = './src/environments/environment.ts'; - -// destination.txt will be created or overwritten by default. -copyFile(sourcePath, targetPath, (err) => { - if (err) throw err; - console.log(sourcePath + ' was copied to ' + targetPath); -}); diff --git a/scripts/test-rest.ts b/scripts/test-rest.ts index b12a9929c2..1584613387 100644 --- a/scripts/test-rest.ts +++ b/scripts/test-rest.ts @@ -1,13 +1,19 @@ import * as http from 'http'; import * as https from 'https'; + import { environment } from '../src/environments/environment'; +// import { AppConfig } from '../src/config/app-config.interface'; +// import { buildAppConfig } from '../src/config/config.server'; + +// const appConfig: AppConfig = buildAppConfig(); + /** * Script to test the connection with the configured REST API (in the 'rest' settings of your environment.*.ts) * * This script is useful to test for any Node.js connection issues with your REST API. * - * Usage (see package.json): yarn test:rest-api + * Usage (see package.json): yarn test:rest */ // Get root URL of configured REST API diff --git a/server.ts b/server.ts index c00bdb5ef5..9a48a91ffb 100644 --- a/server.ts +++ b/server.ts @@ -40,6 +40,9 @@ import { UIServerConfig } from './src/config/ui-server-config.interface'; import { ServerAppModule } from './src/main.server'; +// import { buildAppConfig } from './src/config/config.server'; +// import { AppConfig, APP_CONFIG } from './src/config/app-config.interface'; + /* * Set path for the browser application's dist folder */ @@ -51,6 +54,8 @@ const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : ' const cookieParser = require('cookie-parser'); +// const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/appConfig.json')); + // The Express app is exported so that it can be used by serverless Functions. export function app() { @@ -100,7 +105,11 @@ export function app() { provide: RESPONSE, useValue: (options as any).req.res, }, - ], + // { + // provide: APP_CONFIG, + // useValue: appConfig + // } + ] })(_, (options as any), callback) ); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6f06a84144..bf096c210f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,5 @@ import { distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, @@ -19,6 +20,7 @@ import { import { BehaviorSubject, Observable, of } from 'rxjs'; import { select, Store } from '@ngrx/store'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; @@ -39,13 +41,11 @@ import { LocaleService } from './core/locale/locale.service'; import { hasValue, isNotEmpty } from './shared/empty.util'; import { KlaroService } from './shared/cookies/klaro.service'; import { GoogleAnalyticsService } from './statistics/google-analytics.service'; -import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { ThemeService } from './shared/theme-support/theme.service'; import { BASE_THEME_NAME } from './shared/theme-support/theme.constants'; -import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { getDefaultThemeConfig } from '../config/config.util'; @Component({ selector: 'ds-app', @@ -116,10 +116,13 @@ export class AppComponent implements OnInit, AfterViewInit { } if (hasValue(themeName)) { this.setThemeCss(themeName); - } else if (hasValue(DEFAULT_THEME_CONFIG)) { - this.setThemeCss(DEFAULT_THEME_CONFIG.name); } else { - this.setThemeCss(BASE_THEME_NAME); + const defaultThemeConfig = getDefaultThemeConfig(); + if (hasValue(defaultThemeConfig)) { + this.setThemeCss(defaultThemeConfig.name); + } else { + this.setThemeCss(BASE_THEME_NAME); + } } }); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e2cb10691b..a1c5addae8 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,8 @@ import { APP_BASE_HREF, CommonModule } from '@angular/common'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { EffectsModule } from '@ngrx/effects'; @@ -37,7 +39,6 @@ import { NotificationsBoardComponent } from './shared/notifications/notification import { SharedModule } from './shared/shared.module'; import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component'; import { environment } from '../environments/environment'; -import { BrowserModule } from '@angular/platform-browser'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { AuthInterceptor } from './core/auth/auth.interceptor'; import { LocaleInterceptor } from './core/locale/locale.interceptor'; @@ -56,7 +57,8 @@ import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { UUIDService } from './core/shared/uuid.service'; import { CookieService } from './core/services/cookie.service'; -import { AbstractControl } from '@angular/forms'; + +// import { AppConfig, APP_CONFIG } from '../config/app-config.interface'; export function getBase() { return environment.ui.nameSpace; @@ -100,11 +102,13 @@ IMPORTS.push( const PROVIDERS = [ { provide: APP_BASE_HREF, - useFactory: (getBase) + useFactory: getBase, + // deps: [APP_CONFIG] }, { provide: USER_PROVIDED_META_REDUCERS, useFactory: getMetaReducers, + // deps: [APP_CONFIG] }, { provide: RouterStateSerializer, @@ -117,7 +121,7 @@ const PROVIDERS = [ useFactory: (store: Store,) => { return () => store.dispatch(new CheckAuthenticationTokenAction()); }, - deps: [ Store ], + deps: [Store], multi: true }, // register AuthInterceptor as HttpInterceptor @@ -146,7 +150,7 @@ const PROVIDERS = [ }, // insert the unique id of the user that is using the application utilizing cookies { - provide: APP_INITIALIZER, + provide: APP_INITIALIZER, useFactory: (cookieService: CookieService, uuidService: UUIDService) => { const correlationId = cookieService.get('CORRELATION-ID'); @@ -156,8 +160,8 @@ const PROVIDERS = [ } return () => true; }, - multi: true, - deps: [ CookieService, UUIDService ] + multi: true, + deps: [CookieService, UUIDService] }, { provide: DYNAMIC_ERROR_MESSAGES_MATCHER, diff --git a/src/app/community-page/edit-community-page/community-authorizations/community-authorizations.component.ts b/src/app/community-page/edit-community-page/community-authorizations/community-authorizations.component.ts index 8b241af667..7a9f224311 100644 --- a/src/app/community-page/edit-community-page/community-authorizations/community-authorizations.component.ts +++ b/src/app/community-page/edit-community-page/community-authorizations/community-authorizations.component.ts @@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { RemoteData } from 'src/app/core/data/remote-data'; -import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; +import { RemoteData } from '../../../core/data/remote-data'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; @Component({ selector: 'ds-community-authorizations', diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 667e4a0434..a29c99d326 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -20,7 +20,7 @@ export enum IdentifierType { } export abstract class RestRequest { - public responseMsToLive = environment.cache.msToLive.default; + public responseMsToLive; public isMultipart = false; constructor( @@ -30,6 +30,7 @@ export abstract class RestRequest { public body?: any, public options?: HttpOptions, ) { + this.responseMsToLive = environment.cache.msToLive.default; } getResponseParser(): GenericConstructor { diff --git a/src/app/core/shared/hal-endpoint.service.spec.ts b/src/app/core/shared/hal-endpoint.service.spec.ts index 8b71954cbe..b29b8f662e 100644 --- a/src/app/core/shared/hal-endpoint.service.spec.ts +++ b/src/app/core/shared/hal-endpoint.service.spec.ts @@ -89,7 +89,7 @@ describe('HALEndpointService', () => { describe('getRootEndpointMap', () => { it('should send a new EndpointMapRequest', () => { (service as any).getRootEndpointMap(); - const expected = new EndpointMapRequest(requestService.generateRequestId(), environment.rest.baseUrl + 'api'); + const expected = new EndpointMapRequest(requestService.generateRequestId(), `${environment.rest.baseUrl}/api`); expect(requestService.send).toHaveBeenCalledWith(expected, true); }); diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts index 7f66eb052c..5cc6397118 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts @@ -32,11 +32,11 @@ import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-p import { EPersonMock } from '../../testing/eperson.mock'; import { isNotEmptyOperator } from '../../empty.util'; import { ActivatedRoute, Router } from '@angular/router'; -import { RemoteData } from 'src/app/core/data/remote-data'; +import { RemoteData } from '../../../core/data/remote-data'; import { RouterMock } from '../../mocks/router.mock'; import { Store } from '@ngrx/store'; import { PaginationServiceStub } from '../../testing/pagination-service.stub'; -import { PaginationService } from 'src/app/core/pagination/pagination.service'; +import { PaginationService } from '../../../core/pagination/pagination.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { StoreMock } from '../../testing/store.mock'; diff --git a/src/app/shared/theme-support/theme.effects.ts b/src/app/shared/theme-support/theme.effects.ts index e120257728..df74818fa8 100644 --- a/src/app/shared/theme-support/theme.effects.ts +++ b/src/app/shared/theme-support/theme.effects.ts @@ -2,15 +2,9 @@ import { Injectable } from '@angular/core'; import { createEffect, Actions, ofType, ROOT_EFFECTS_INIT } from '@ngrx/effects'; import { map } from 'rxjs/operators'; import { SetThemeAction } from './theme.actions'; -import { environment } from '../../../environments/environment'; -import { hasValue, hasNoValue } from '../empty.util'; +import { hasValue } from '../empty.util'; import { BASE_THEME_NAME } from './theme.constants'; - -export const DEFAULT_THEME_CONFIG = environment.themes.find((themeConfig: any) => - hasNoValue(themeConfig.regex) && - hasNoValue(themeConfig.handle) && - hasNoValue(themeConfig.uuid) -); +import { getDefaultThemeConfig } from '../../../config/config.util'; @Injectable() export class ThemeEffects { @@ -21,8 +15,9 @@ export class ThemeEffects { this.actions$.pipe( ofType(ROOT_EFFECTS_INIT), map(() => { - if (hasValue(DEFAULT_THEME_CONFIG)) { - return new SetThemeAction(DEFAULT_THEME_CONFIG.name); + const defaultThemeConfig = getDefaultThemeConfig(); + if (hasValue(defaultThemeConfig)) { + return new SetThemeAction(defaultThemeConfig.name); } else { return new SetThemeAction(BASE_THEME_NAME); } diff --git a/src/config/config.ts b/src/config/config.server.ts similarity index 55% rename from src/config/config.ts rename to src/config/config.server.ts index d695c141f8..355ba028ab 100644 --- a/src/config/config.ts +++ b/src/config/config.server.ts @@ -1,10 +1,15 @@ import * as colors from 'colors'; import * as fs from 'fs'; import { join } from 'path'; + +import { environment } from '../environments/environment'; + import { AppConfig } from './app-config.interface'; import { Config } from './config.interface'; import { DefaultAppConfig } from './default-app-config'; import { ServerConfig } from './server-config.interface'; +import { extendConfig, extendEnvironmentWithAppConfig } from './config.util'; +import { isNotEmpty } from '../app/shared/empty.util'; const CONFIG_PATH = join(process.cwd(), 'config'); @@ -12,9 +17,17 @@ const APP_CONFIG_PATH = join(CONFIG_PATH, 'appConfig.json'); type Environment = 'production' | 'development' | 'test'; +const getBooleanFromString = (variable: string): boolean => { + return variable === 'true' || variable === '1'; +}; + +const getNumberFromString = (variable: string): number => { + return Number(variable); +}; + const getEnvironment = (): Environment => { let environment: Environment = 'development'; - if (!!process.env.NODE_ENV) { + if (isNotEmpty(process.env.NODE_ENV)) { switch (process.env.NODE_ENV) { case 'prod': case 'production': @@ -34,7 +47,10 @@ const getEnvironment = (): Environment => { return environment; }; -const getDistConfigPath = (env: Environment) => { +const getLocalConfigPath = (env: Environment) => { + // default to config/appConfig.json + let localConfigPath = APP_CONFIG_PATH; + // determine app config filename variations let envVariations; switch (env) { @@ -53,19 +69,18 @@ const getDistConfigPath = (env: Environment) => { for (const envVariation of envVariations) { const altDistConfigPath = join(CONFIG_PATH, `appConfig.${envVariation}.json`); if (fs.existsSync(altDistConfigPath)) { - return altDistConfigPath; + localConfigPath = altDistConfigPath; } } - // return default config/appConfig.json - return APP_CONFIG_PATH; + return localConfigPath; }; const overrideWithConfig = (config: Config, pathToConfig: string) => { try { console.log(`Overriding app config with ${pathToConfig}`); const externalConfig = fs.readFileSync(pathToConfig, 'utf8'); - Object.assign(config, JSON.parse(externalConfig)); + extendConfig(config, JSON.parse(externalConfig)); } catch (err) { console.error(err); } @@ -73,15 +88,27 @@ const overrideWithConfig = (config: Config, pathToConfig: string) => { const overrideWithEnvironment = (config: Config, key: string = '') => { for (const property in config) { - const variable = `${key}${!!key ? '_' : ''}${property.toUpperCase()}`; + const variable = `${key}${isNotEmpty(key) ? '_' : ''}${property.toUpperCase()}`; const innerConfig = config[property]; - if (!!innerConfig) { + if (isNotEmpty(innerConfig)) { if (typeof innerConfig === 'object') { overrideWithEnvironment(innerConfig, variable); } else { - if (!!process.env[variable]) { + if (isNotEmpty(process.env[variable])) { console.log(`Applying environment variable ${variable} with value ${process.env[variable]}`); - config[property] = process.env[variable]; + switch (typeof innerConfig) { + case 'number': + config[property] = getNumberFromString(process.env[variable]); + break; + case 'boolean': + config[property] = getBooleanFromString(process.env[variable]); + break; + case 'string': + config[property] = process.env[variable]; + default: + console.warn(`Unsupported environment variable type ${typeof innerConfig} ${variable}`); + } + } } } @@ -97,7 +124,7 @@ const buildBaseUrl = (config: ServerConfig): void => { ].join(''); }; -export const buildAppConfig = (destConfigPath: string): AppConfig => { +export const buildAppConfig = (destConfigPath?: string): AppConfig => { // start with default app config const appConfig: AppConfig = new DefaultAppConfig(); @@ -116,7 +143,7 @@ export const buildAppConfig = (destConfigPath: string): AppConfig => { } // override with dist config - const distConfigPath = getDistConfigPath(env); + const distConfigPath = getLocalConfigPath(env); if (fs.existsSync(distConfigPath)) { overrideWithConfig(appConfig, distConfigPath); } else { @@ -125,7 +152,7 @@ export const buildAppConfig = (destConfigPath: string): AppConfig => { // override with external config if specified by environment variable `APP_CONFIG_PATH` const externalConfigPath = process.env.APP_CONFIG_PATH; - if (!!externalConfigPath) { + if (isNotEmpty(externalConfigPath)) { if (fs.existsSync(externalConfigPath)) { overrideWithConfig(appConfig, externalConfigPath); } else { @@ -136,6 +163,18 @@ export const buildAppConfig = (destConfigPath: string): AppConfig => { // override with environment variables overrideWithEnvironment(appConfig); + // apply existing non convention UI environment variables + appConfig.ui.host = isNotEmpty(process.env.DSPACE_HOST) ? process.env.DSPACE_HOST : appConfig.ui.host; + appConfig.ui.port = isNotEmpty(process.env.DSPACE_PORT) ? getNumberFromString(process.env.DSPACE_PORT) : appConfig.ui.port; + appConfig.ui.nameSpace = isNotEmpty(process.env.DSPACE_NAMESPACE) ? process.env.DSPACE_NAMESPACE : appConfig.ui.nameSpace; + appConfig.ui.ssl = isNotEmpty(process.env.DSPACE_SSL) ? getBooleanFromString(process.env.DSPACE_SSL) : appConfig.ui.ssl; + + // apply existing non convention REST environment variables + appConfig.rest.host = isNotEmpty(process.env.DSPACE_REST_HOST) ? process.env.DSPACE_REST_HOST : appConfig.rest.host; + appConfig.rest.port = isNotEmpty(process.env.DSPACE_REST_PORT) ? getNumberFromString(process.env.DSPACE_REST_PORT) : appConfig.rest.port; + appConfig.rest.nameSpace = isNotEmpty(process.env.DSPACE_REST_NAMESPACE) ? process.env.DSPACE_REST_NAMESPACE : appConfig.rest.nameSpace; + appConfig.rest.ssl = isNotEmpty(process.env.DSPACE_REST_SSL) ? getBooleanFromString(process.env.DSPACE_REST_SSL) : appConfig.rest.ssl; + // apply build defined production appConfig.production = env === 'production'; @@ -143,9 +182,14 @@ export const buildAppConfig = (destConfigPath: string): AppConfig => { buildBaseUrl(appConfig.ui); buildBaseUrl(appConfig.rest); - fs.writeFileSync(destConfigPath, JSON.stringify(appConfig, null, 2)); + // extend environment with app config for server side use + extendEnvironmentWithAppConfig(environment, appConfig); - console.log(`Angular ${colors.bold('appConfig.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); + if (isNotEmpty(destConfigPath)) { + fs.writeFileSync(destConfigPath, JSON.stringify(appConfig, null, 2)); + + console.log(`Angular ${colors.bold('appConfig.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); + } return appConfig; -} +}; diff --git a/src/config/config.util.ts b/src/config/config.util.ts new file mode 100644 index 0000000000..b576e99eba --- /dev/null +++ b/src/config/config.util.ts @@ -0,0 +1,37 @@ +import * as merge from 'deepmerge'; + +import { environment } from '../environments/environment'; + +import { hasNoValue } from '../app/shared/empty.util'; + +import { AppConfig } from './app-config.interface'; +import { ThemeConfig } from './theme.model'; + +const extendEnvironmentWithAppConfig = (env: any, appConfig: AppConfig): void => { + extendConfig(env, appConfig); + console.log(`Environment extended with app config`); +}; + +const extendConfig = (config: any, appConfig: AppConfig): void => { + const mergeOptions = { + arrayMerge: (destinationArray, sourceArray, options) => sourceArray + }; + Object.assign(config, merge.all([ + config, + appConfig + ], mergeOptions)); +}; + +const getDefaultThemeConfig = (): ThemeConfig => { + return environment.themes.find((themeConfig: any) => + hasNoValue(themeConfig.regex) && + hasNoValue(themeConfig.handle) && + hasNoValue(themeConfig.uuid) + ); +}; + +export { + extendEnvironmentWithAppConfig, + extendConfig, + getDefaultThemeConfig +}; diff --git a/src/environments/environment.ci.ts b/src/environments/environment.ci.ts new file mode 100644 index 0000000000..bb12eb14c1 --- /dev/null +++ b/src/environments/environment.ci.ts @@ -0,0 +1,294 @@ +import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { RestRequestMethod } from '../app/core/data/rest-request-method'; +import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; +import { AppConfig } from '../config/app-config.interface'; + +export const environment: AppConfig = { + production: true, + + // Angular Universal settings + universal: { + preboot: true, + async: true, + time: false + }, + + // Angular Universal server settings. + // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. + ui: { + ssl: false, + host: 'localhost', + port: 4000, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/', + baseUrl: 'http://localhost:4000/', + + // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). + rateLimiter: { + windowMs: 1 * 60 * 1000, // 1 minute + max: 500 // limit each IP to 500 requests per windowMs + } + }, + + // The REST API server settings. + // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. + rest: { + ssl: false, + host: 'localhost', + port: 8080, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/server', + baseUrl: 'http://localhost:8080/server' + }, + + // Caching settings + cache: { + // NOTE: how long should objects be cached for by default + msToLive: { + default: 15 * 60 * 1000 // 15 minutes + }, + control: 'max-age=60', // revalidate browser + autoSync: { + defaultTime: 0, + maxBufferSize: 100, + timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds + } + }, + + // Authentication settings + auth: { + // Authentication UI settings + ui: { + // the amount of time before the idle warning is shown + timeUntilIdle: 15 * 60 * 1000, // 15 minutes + // the amount of time the user has to react after the idle warning is shown before they are logged out. + idleGracePeriod: 5 * 60 * 1000 // 5 minutes + }, + // Authentication REST settings + rest: { + // If the rest token expires in less than this amount of time, it will be refreshed automatically. + // This is independent from the idle warning. + timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes + } + }, + + // Form settings + form: { + // NOTE: Map server-side validators to comparative Angular form validators + validatorMap: { + required: 'required', + regex: 'pattern' + } + }, + + // Notifications + notifications: { + rtl: false, + position: ['top', 'right'], + maxStack: 8, + // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically + timeOut: 5000, // 5 second + clickToClose: true, + // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' + animate: NotificationAnimationsType.Scale + }, + + // Submission settings + submission: { + autosave: { + // NOTE: which metadata trigger an autosave + metadata: [], + /** + * NOTE: after how many time (milliseconds) submission is saved automatically + * eg. timer: 5 * (1000 * 60); // 5 minutes + */ + timer: 0 + }, + icons: { + metadata: [ + /** + * NOTE: example of configuration + * { + * // NOTE: metadata name + * name: 'dc.author', + * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + * style: 'fa-user' + * } + */ + { + name: 'dc.author', + style: 'fas fa-user' + }, + // default configuration + { + name: 'default', + style: '' + } + ], + authority: { + confidence: [ + /** + * NOTE: example of configuration + * { + * // NOTE: confidence value + * value: 'dc.author', + * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used + * style: 'fa-user' + * } + */ + { + value: 600, + style: 'text-success' + }, + { + value: 500, + style: 'text-info' + }, + { + value: 400, + style: 'text-warning' + }, + // default configuration + { + value: 'default', + style: 'text-muted' + } + + ] + } + } + }, + + // NOTE: will log all redux actions and transfers in console + debug: false, + + // Default Language in which the UI will be rendered if the user's browser language is not an active language + defaultLanguage: 'en', + + // Languages. DSpace Angular holds a message catalog for each of the following languages. + // When set to active, users will be able to switch to the use of this language in the user interface. + languages: [ + { code: 'en', label: 'English', active: true }, + { code: 'cs', label: 'Čeština', active: true }, + { code: 'de', label: 'Deutsch', active: true }, + { code: 'es', label: 'Español', active: true }, + { code: 'fr', label: 'Français', active: true }, + { code: 'lv', label: 'Latviešu', active: true }, + { code: 'hu', label: 'Magyar', active: true }, + { code: 'nl', label: 'Nederlands', active: true }, + { code: 'pt-PT', label: 'Português', active: true }, + { code: 'pt-BR', label: 'Português do Brasil', active: true }, + { code: 'fi', label: 'Suomi', active: true } + ], + + // Browse-By Pages + browseBy: { + // Amount of years to display using jumps of one year (current year - oneYearLimit) + oneYearLimit: 10, + // Limit for years to display using jumps of five years (current year - fiveYearLimit) + fiveYearLimit: 30, + // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) + defaultLowerLimit: 1900, + // List of all the active Browse-By types + // Adding a type will activate their Browse-By page and add them to the global navigation menu, + // as well as community and collection pages + // Allowed fields and their purpose: + // id: The browse id to use for fetching info from the rest api + // type: The type of Browse-By page to display + // metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') + types: [ + { + id: 'title', + type: BrowseByType.Title, + }, + { + id: 'dateissued', + type: BrowseByType.Date, + metadataField: 'dc.date.issued' + }, + { + id: 'author', + type: BrowseByType.Metadata + }, + { + id: 'subject', + type: BrowseByType.Metadata + } + ] + }, + + // Item Page Config + item: { + edit: { + undoTimeout: 10000 // 10 seconds + } + }, + + // Collection Page Config + collection: { + edit: { + undoTimeout: 10000 // 10 seconds + } + }, + + // Theme Config + themes: [ + // Add additional themes here. In the case where multiple themes match a route, the first one + // in this list will get priority. It is advisable to always have a theme that matches + // every route as the last one + + // { + // // A theme with a handle property will match the community, collection or item with the given + // // handle, and all collections and/or items within it + // name: 'custom', + // handle: '10673/1233' + // }, + // { + // // A theme with a regex property will match the route using a regular expression. If it + // // matches the route for a community or collection it will also apply to all collections + // // and/or items within it + // name: 'custom', + // regex: 'collections\/e8043bc2.*' + // }, + // { + // // A theme with a uuid property will match the community, collection or item with the given + // // ID, and all collections and/or items within it + // name: 'custom', + // uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' + // }, + // { + // // The extends property specifies an ancestor theme (by name). Whenever a themed component is not found + // // in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. + // name: 'custom-A', + // extends: 'custom-B', + // // Any of the matching properties above can be used + // handle: '10673/34' + // }, + // { + // name: 'custom-B', + // extends: 'custom', + // handle: '10673/12' + // }, + // { + // // A theme with only a name will match every route + // name: 'custom' + // }, + // { + // // This theme will use the default bootstrap styling for DSpace components + // name: BASE_THEME_NAME + // }, + + { + // The default dspace theme + name: 'dspace' + } + ], + + // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). + // For images, this enables a gallery viewer where you can zoom or page through images. + // For videos, this enables embedded video streaming + mediaViewer: { + image: false, + video: false + } +}; diff --git a/src/environments/environment.common.ts b/src/environments/environment.prod.ts similarity index 84% rename from src/environments/environment.common.ts rename to src/environments/environment.prod.ts index 6bb2c8dc51..63a9e01a2e 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.prod.ts @@ -1,47 +1,60 @@ -import { AppConfig } from '../config/app-config.interface'; -import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; import { RestRequestMethod } from '../app/core/data/rest-request-method'; +import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; +import { AppConfig } from '../config/app-config.interface'; export const environment: AppConfig = { production: true, + + // Angular Universal settings + universal: { + preboot: true, + async: true, + time: false + }, + // Angular Universal server settings. - // NOTE: these must be "synced" with the 'dspace.ui.url' setting in your backend's local.cfg. + // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. ui: { ssl: false, host: 'localhost', port: 4000, // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: '/', - // The rateLimiter settings limit each IP to a "max" of 500 requests per "windowMs" (1 minute). + baseUrl: 'http://localhost:4000/', + + // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). rateLimiter: { - windowMs: 1 * 60 * 1000, // 1 minute + windowMs: 1 * 60 * 1000, // 1 minute max: 500 // limit each IP to 500 requests per windowMs } }, + // The REST API server settings. - // NOTE: these must be "synced" with the 'dspace.server.url' setting in your backend's local.cfg. + // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. rest: { ssl: true, host: 'api7.dspace.org', port: 443, // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: '/server', + baseUrl: 'https://api7.dspace.org/server' }, + // Caching settings cache: { // NOTE: how long should objects be cached for by default msToLive: { - default: 15 * 60 * 1000, // 15 minutes + default: 15 * 60 * 1000 // 15 minutes }, - // msToLive: 1000, // 15 minutes control: 'max-age=60', // revalidate browser autoSync: { defaultTime: 0, maxBufferSize: 100, - timePerMethod: {[RestRequestMethod.PATCH]: 3} as any // time in seconds + timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds } }, + // Authentication settings auth: { // Authentication UI settings @@ -49,15 +62,16 @@ export const environment: AppConfig = { // the amount of time before the idle warning is shown timeUntilIdle: 15 * 60 * 1000, // 15 minutes // the amount of time the user has to react after the idle warning is shown before they are logged out. - idleGracePeriod: 5 * 60 * 1000, // 5 minutes + idleGracePeriod: 5 * 60 * 1000 // 5 minutes }, // Authentication REST settings rest: { // If the rest token expires in less than this amount of time, it will be refreshed automatically. // This is independent from the idle warning. - timeLeftBeforeTokenRefresh: 2 * 60 * 1000, // 2 minutes - }, + timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes + } }, + // Form settings form: { // NOTE: Map server-side validators to comparative Angular form validators @@ -66,6 +80,7 @@ export const environment: AppConfig = { regex: 'pattern' } }, + // Notifications notifications: { rtl: false, @@ -77,6 +92,7 @@ export const environment: AppConfig = { // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' animate: NotificationAnimationsType.Scale }, + // Submission settings submission: { autosave: { @@ -136,69 +152,35 @@ export const environment: AppConfig = { { value: 'default', style: 'text-muted' - }, + } ] } } }, - // Angular Universal settings - universal: { - preboot: true, - async: true, - time: false - }, + // NOTE: will log all redux actions and transfers in console debug: false, + // Default Language in which the UI will be rendered if the user's browser language is not an active language defaultLanguage: 'en', + // Languages. DSpace Angular holds a message catalog for each of the following languages. // When set to active, users will be able to switch to the use of this language in the user interface. - languages: [{ - code: 'en', - label: 'English', - active: true, - }, { - code: 'cs', - label: 'Čeština', - active: true, - }, { - code: 'de', - label: 'Deutsch', - active: true, - }, { - code: 'es', - label: 'Español', - active: true, - }, { - code: 'fr', - label: 'Français', - active: true, - }, { - code: 'lv', - label: 'Latviešu', - active: true, - }, { - code: 'hu', - label: 'Magyar', - active: true, - }, { - code: 'nl', - label: 'Nederlands', - active: true, - }, { - code: 'pt-PT', - label: 'Português', - active: true, - },{ - code: 'pt-BR', - label: 'Português do Brasil', - active: true, - },{ - code: 'fi', - label: 'Suomi', - active: true, - }], + languages: [ + { code: 'en', label: 'English', active: true }, + { code: 'cs', label: 'Čeština', active: true }, + { code: 'de', label: 'Deutsch', active: true }, + { code: 'es', label: 'Español', active: true }, + { code: 'fr', label: 'Français', active: true }, + { code: 'lv', label: 'Latviešu', active: true }, + { code: 'hu', label: 'Magyar', active: true }, + { code: 'nl', label: 'Nederlands', active: true }, + { code: 'pt-PT', label: 'Português', active: true }, + { code: 'pt-BR', label: 'Português do Brasil', active: true }, + { code: 'fi', label: 'Suomi', active: true } + ], + // Browse-By Pages browseBy: { // Amount of years to display using jumps of one year (current year - oneYearLimit) @@ -234,16 +216,22 @@ export const environment: AppConfig = { } ] }, + + // Item Page Config item: { edit: { undoTimeout: 10000 // 10 seconds } }, + + // Collection Page Config collection: { edit: { undoTimeout: 10000 // 10 seconds } }, + + // Theme Config themes: [ // Add additional themes here. In the case where multiple themes match a route, the first one // in this list will get priority. It is advisable to always have a theme that matches @@ -274,12 +262,12 @@ export const environment: AppConfig = { // name: 'custom-A', // extends: 'custom-B', // // Any of the matching properties above can be used - // handle: '10673/34', + // handle: '10673/34' // }, // { // name: 'custom-B', // extends: 'custom', - // handle: '10673/12', + // handle: '10673/12' // }, // { // // A theme with only a name will match every route @@ -293,13 +281,14 @@ export const environment: AppConfig = { { // The default dspace theme name: 'dspace' - }, + } ], - // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with "image" or "video"). + + // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). // For images, this enables a gallery viewer where you can zoom or page through images. // For videos, this enables embedded video streaming mediaViewer: { image: false, - video: false, - }, + video: false + } }; diff --git a/src/environments/environment.template.ts b/src/environments/environment.template.ts deleted file mode 100644 index 6f6510e1ab..0000000000 --- a/src/environments/environment.template.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const environment = { - /** - * TODO add the sections from environment.common.ts you want to override here - * e.g. - * rest: { - * host: 'rest.api', - * nameSpace: '/rest', - * } - */ -}; diff --git a/src/environments/mock-environment.ts b/src/environments/environment.test.ts similarity index 90% rename from src/environments/mock-environment.ts rename to src/environments/environment.test.ts index 02c1012c68..394a2e5dac 100644 --- a/src/environments/mock-environment.ts +++ b/src/environments/environment.test.ts @@ -1,46 +1,66 @@ -// This configuration is only used for unit tests, end-to-end tests use environment.prod.ts +// This configuration is only used for unit tests, end-to-end tests use environment.e2e.ts import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; import { RestRequestMethod } from '../app/core/data/rest-request-method'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; import { AppConfig } from '../config/app-config.interface'; -export const environment: Partial = { - rest: { - ssl: true, - host: 'rest.com', - port: 443, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/api', - baseUrl: 'https://rest.api/' +export const environment: AppConfig = { + production: false, + + // Angular Universal settings + universal: { + preboot: true, + async: true, + time: false }, + + // Angular Universal server settings. ui: { ssl: false, host: 'dspace.com', port: 80, // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: '/angular-dspace', - rateLimiter: undefined + baseUrl: 'http://dspace.com/angular-dspace', + // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). + rateLimiter: { + windowMs: 1 * 60 * 1000, // 1 minute + max: 500 // limit each IP to 500 requests per windowMs + } }, - // Caching settings + + // The REST API server settings. + rest: { + ssl: true, + host: 'rest.com', + port: 443, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/api', + baseUrl: 'https://rest.com/api' + }, + + // Caching settings cache: { // NOTE: how long should objects be cached for by default msToLive: { default: 15 * 60 * 1000, // 15 minutes }, // msToLive: 1000, // 15 minutes - control: 'max-age=60', // revalidate browser + control: 'max-age=60', autoSync: { defaultTime: 0, maxBufferSize: 100, - timePerMethod: {[RestRequestMethod.PATCH]: 3} as any // time in seconds + timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds } }, + // Authentication settings auth: { // Authentication UI settings ui: { // the amount of time before the idle warning is shown - timeUntilIdle: 20000, // 20 sec + timeUntilIdle: 20000, + // the amount of time the user has to react after the idle warning is shown before they are logged out. idleGracePeriod: 20000, // 20 sec }, @@ -51,6 +71,7 @@ export const environment: Partial = { timeLeftBeforeTokenRefresh: 20000, // 20 sec }, }, + // Form settings form: { // NOTE: Map server-side validators to comparative Angular form validators @@ -59,17 +80,19 @@ export const environment: Partial = { regex: 'pattern' } }, + // Notifications notifications: { rtl: false, position: ['top', 'right'], maxStack: 8, // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically - timeOut: 5000, // 5 second + timeOut: 5000, clickToClose: true, // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' animate: NotificationAnimationsType.Scale }, + // Submission settings submission: { autosave: { @@ -115,21 +138,17 @@ export const environment: Partial = { value: 'default', style: 'text-muted' }, - ] } } }, - // Angular Universal settings - universal: { - preboot: true, - async: true, - time: false - }, + // NOTE: will log all redux actions and transfers in console debug: false, + // Default Language in which the UI will be rendered if the user's browser language is not an active language defaultLanguage: 'en', + // Languages. DSpace Angular holds a message catalog for each of the following languages. // When set to active, users will be able to switch to the use of this language in the user interface. languages: [{ @@ -161,6 +180,7 @@ export const environment: Partial = { label: 'Latviešu', active: true, }], + // Browse-By Pages browseBy: { // Amount of years to display using jumps of one year (current year - oneYearLimit) @@ -234,5 +254,5 @@ export const environment: Partial = { mediaViewer: { image: true, video: true - }, + } }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000000..ba7b262ac4 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,309 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --configuration production` replaces `environment.ts` with `environment.prod.ts`. +// `ng build --configuration ci` replaces `environment.ts` with `environment.ci.ts`. +// `ng test --configuration test` replaces `environment.ts` with `environment.test.ts`. +// The list of file replacements can be found in `angular.json`. + +import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { RestRequestMethod } from '../app/core/data/rest-request-method'; +import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; +import { AppConfig } from '../config/app-config.interface'; + +export const environment: AppConfig = { + production: false, + + // Angular Universal settings + universal: { + preboot: true, + async: true, + time: false + }, + + // Angular Universal server settings. + // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. + ui: { + ssl: false, + host: 'localhost', + port: 4000, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/', + baseUrl: 'http://localhost:4000/', + + // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). + rateLimiter: { + windowMs: 1 * 60 * 1000, // 1 minute + max: 500 // limit each IP to 500 requests per windowMs + } + }, + + // The REST API server settings. + // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. + rest: { + ssl: true, + host: 'api7.dspace.org', + port: 443, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/server', + baseUrl: 'https://api7.dspace.org/server' + }, + + // Caching settings + cache: { + // NOTE: how long should objects be cached for by default + msToLive: { + default: 15 * 60 * 1000 // 15 minutes + }, + control: 'max-age=60', // revalidate browser + autoSync: { + defaultTime: 0, + maxBufferSize: 100, + timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds + } + }, + + // Authentication settings + auth: { + // Authentication UI settings + ui: { + // the amount of time before the idle warning is shown + timeUntilIdle: 15 * 60 * 1000, // 15 minutes + // the amount of time the user has to react after the idle warning is shown before they are logged out. + idleGracePeriod: 5 * 60 * 1000 // 5 minutes + }, + // Authentication REST settings + rest: { + // If the rest token expires in less than this amount of time, it will be refreshed automatically. + // This is independent from the idle warning. + timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes + } + }, + + // Form settings + form: { + // NOTE: Map server-side validators to comparative Angular form validators + validatorMap: { + required: 'required', + regex: 'pattern' + } + }, + + // Notifications + notifications: { + rtl: false, + position: ['top', 'right'], + maxStack: 8, + // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically + timeOut: 5000, // 5 second + clickToClose: true, + // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' + animate: NotificationAnimationsType.Scale + }, + + // Submission settings + submission: { + autosave: { + // NOTE: which metadata trigger an autosave + metadata: [], + /** + * NOTE: after how many time (milliseconds) submission is saved automatically + * eg. timer: 5 * (1000 * 60); // 5 minutes + */ + timer: 0 + }, + icons: { + metadata: [ + /** + * NOTE: example of configuration + * { + * // NOTE: metadata name + * name: 'dc.author', + * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + * style: 'fa-user' + * } + */ + { + name: 'dc.author', + style: 'fas fa-user' + }, + // default configuration + { + name: 'default', + style: '' + } + ], + authority: { + confidence: [ + /** + * NOTE: example of configuration + * { + * // NOTE: confidence value + * value: 'dc.author', + * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used + * style: 'fa-user' + * } + */ + { + value: 600, + style: 'text-success' + }, + { + value: 500, + style: 'text-info' + }, + { + value: 400, + style: 'text-warning' + }, + // default configuration + { + value: 'default', + style: 'text-muted' + } + + ] + } + } + }, + + // NOTE: will log all redux actions and transfers in console + debug: false, + + // Default Language in which the UI will be rendered if the user's browser language is not an active language + defaultLanguage: 'en', + + // Languages. DSpace Angular holds a message catalog for each of the following languages. + // When set to active, users will be able to switch to the use of this language in the user interface. + languages: [ + { code: 'en', label: 'English', active: true }, + { code: 'cs', label: 'Čeština', active: true }, + { code: 'de', label: 'Deutsch', active: true }, + { code: 'es', label: 'Español', active: true }, + { code: 'fr', label: 'Français', active: true }, + { code: 'lv', label: 'Latviešu', active: true }, + { code: 'hu', label: 'Magyar', active: true }, + { code: 'nl', label: 'Nederlands', active: true }, + { code: 'pt-PT', label: 'Português', active: true }, + { code: 'pt-BR', label: 'Português do Brasil', active: true }, + { code: 'fi', label: 'Suomi', active: true } + ], + + // Browse-By Pages + browseBy: { + // Amount of years to display using jumps of one year (current year - oneYearLimit) + oneYearLimit: 10, + // Limit for years to display using jumps of five years (current year - fiveYearLimit) + fiveYearLimit: 30, + // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) + defaultLowerLimit: 1900, + // List of all the active Browse-By types + // Adding a type will activate their Browse-By page and add them to the global navigation menu, + // as well as community and collection pages + // Allowed fields and their purpose: + // id: The browse id to use for fetching info from the rest api + // type: The type of Browse-By page to display + // metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') + types: [ + { + id: 'title', + type: BrowseByType.Title, + }, + { + id: 'dateissued', + type: BrowseByType.Date, + metadataField: 'dc.date.issued' + }, + { + id: 'author', + type: BrowseByType.Metadata + }, + { + id: 'subject', + type: BrowseByType.Metadata + } + ] + }, + + // Item Page Config + item: { + edit: { + undoTimeout: 10000 // 10 seconds + } + }, + + // Collection Page Config + collection: { + edit: { + undoTimeout: 10000 // 10 seconds + } + }, + + // Theme Config + themes: [ + // Add additional themes here. In the case where multiple themes match a route, the first one + // in this list will get priority. It is advisable to always have a theme that matches + // every route as the last one + + // { + // // A theme with a handle property will match the community, collection or item with the given + // // handle, and all collections and/or items within it + // name: 'custom', + // handle: '10673/1233' + // }, + // { + // // A theme with a regex property will match the route using a regular expression. If it + // // matches the route for a community or collection it will also apply to all collections + // // and/or items within it + // name: 'custom', + // regex: 'collections\/e8043bc2.*' + // }, + // { + // // A theme with a uuid property will match the community, collection or item with the given + // // ID, and all collections and/or items within it + // name: 'custom', + // uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' + // }, + // { + // // The extends property specifies an ancestor theme (by name). Whenever a themed component is not found + // // in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. + // name: 'custom-A', + // extends: 'custom-B', + // // Any of the matching properties above can be used + // handle: '10673/34' + // }, + // { + // name: 'custom-B', + // extends: 'custom', + // handle: '10673/12' + // }, + // { + // // A theme with only a name will match every route + // name: 'custom' + // }, + // { + // // This theme will use the default bootstrap styling for DSpace components + // name: BASE_THEME_NAME + // }, + + { + // The default dspace theme + name: 'dspace' + } + ], + + // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). + // For images, this enables a gallery viewer where you can zoom or page through images. + // For videos, this enables embedded video streaming + mediaViewer: { + image: false, + video: false + } +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/src/main.browser.ts b/src/main.browser.ts index d6c3fd6535..5a17c3e7cc 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -1,22 +1,25 @@ import 'zone.js/dist/zone'; import 'reflect-metadata'; import 'core-js/es/reflect'; + import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { bootloader } from '@angularclass/bootloader'; - import { load as loadWebFont } from 'webfontloader'; + import { hasValue } from './app/shared/empty.util'; import { BrowserAppModule } from './modules/app/browser-app.module'; import { environment } from './environments/environment'; +// import { AppConfig, APP_CONFIG } from './config/app-config.interface'; +// import { extendEnvironmentWithAppConfig } from './config/config.util'; + if (environment.production) { enableProdMode(); } -export function main() { +const main = () => { // Load fonts async // https://github.com/typekit/webfontloader#configuration loadWebFont({ @@ -25,13 +28,23 @@ export function main() { } }); - return platformBrowserDynamic().bootstrapModule(BrowserAppModule, {preserveWhitespaces:true}); -} + return platformBrowserDynamic() + .bootstrapModule(BrowserAppModule, { + preserveWhitespaces: true + }); +}; // support async tag or hmr if (hasValue(environment.universal) && environment.universal.preboot === false) { - bootloader(main); + main(); } else { - document.addEventListener('DOMContentLoaded', () => bootloader(main)); + document.addEventListener('DOMContentLoaded', main); } + +// fetch('assets/appConfig.json') +// .then((response) => response.json()) +// .then((appConfig: AppConfig) => { +// // extend environment with app config for client side use +// extendEnvironmentWithAppConfig(environment, appConfig); +// }); diff --git a/webpack/webpack.browser.ts b/webpack/webpack.browser.ts index 3c1d01dda0..187262071e 100644 --- a/webpack/webpack.browser.ts +++ b/webpack/webpack.browser.ts @@ -1,6 +1,7 @@ -import { buildAppConfig } from '../src/config/config'; +// import { join } from 'path'; + +// import { buildAppConfig } from '../src/config/config.server'; import { commonExports } from './webpack.common'; -import { join } from 'path'; module.exports = Object.assign({}, commonExports, { target: 'web', @@ -9,7 +10,7 @@ module.exports = Object.assign({}, commonExports, { }, devServer: { before(app, server) { - buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); + // buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); } } }); diff --git a/yarn.lock b/yarn.lock index fdb758af01..e8e9b67c4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -427,11 +427,6 @@ dependencies: tslib "^2.0.0" -"@angularclass/bootloader@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@angularclass/bootloader/-/bootloader-1.0.1.tgz#75de7cf3901b445900a419c2aeca44181d465060" - integrity sha1-dd5885AbRFkApBnCrspEGB1GUGA= - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.8.3": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" @@ -1941,18 +1936,6 @@ semver "7.3.4" semver-intersect "1.4.0" -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2630,13 +2613,6 @@ angulartics2@^10.0.0: dependencies: tslib "^2.0.0" -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - ansi-colors@4.1.1, ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -3201,20 +3177,6 @@ bootstrap@4.3.1: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac" integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag== -boxen@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" - integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^6.2.0" - chalk "^4.1.0" - cli-boxes "^2.2.1" - string-width "^4.2.2" - type-fest "^0.20.2" - widest-line "^3.1.0" - wrap-ansi "^7.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3582,19 +3544,6 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -3774,11 +3723,6 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - ci-info@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -3834,11 +3778,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -3918,13 +3857,6 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -4153,18 +4085,6 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - connect-history-api-fallback@^1, connect-history-api-fallback@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" @@ -4470,7 +4390,7 @@ critters@0.0.7: parse5-htmlparser2-tree-adapter "^6.0.1" pretty-bytes "^5.3.0" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -4512,11 +4432,6 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" @@ -5032,7 +4947,7 @@ debug@4.3.1: dependencies: ms "2.1.2" -debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6, debug@^3.2.7: +debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -5068,13 +4983,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -5087,11 +4995,6 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-freeze@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" @@ -5129,11 +5032,6 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -5451,11 +5349,6 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5769,11 +5662,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -6492,7 +6380,7 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@^4.0.0, get-stream@^4.1.0: +get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -6636,23 +6524,6 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" @@ -6772,11 +6643,6 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -6827,11 +6693,6 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: dependencies: react-is "^16.7.0" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - hosted-git-info@^3.0.6: version "3.0.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" @@ -6953,7 +6814,7 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: +http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -7168,11 +7029,6 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= - ignore-walk@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" @@ -7249,11 +7105,6 @@ import-from@^3.0.0: dependencies: resolve-from "^5.0.0" -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -7323,7 +7174,7 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.4, ini@~1.3.0: +ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -7497,13 +7348,6 @@ is-callable@^1.1.4, is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - is-ci@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" @@ -7632,7 +7476,7 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= -is-installed-globally@^0.4.0, is-installed-globally@~0.4.0: +is-installed-globally@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== @@ -7662,11 +7506,6 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-npm@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" - integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== - is-number-like@^1.0.3: version "1.0.8" resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" @@ -7805,7 +7644,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -7844,11 +7683,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -8077,11 +7911,6 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -8358,13 +8187,6 @@ karma@^5.2.3: ua-parser-js "0.7.22" yargs "^15.3.1" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -8417,13 +8239,6 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" -latest-version@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -8553,16 +8368,6 @@ listr2@^3.8.3: through "^2.3.8" wrap-ansi "^7.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -8749,16 +8554,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -8904,11 +8699,6 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -9008,11 +8798,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - mini-css-extract-plugin@0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.10.0.tgz#a0e6bfcad22a9c73f6c882a3c7557a98e2d3d27d" @@ -9457,22 +9242,6 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== -nodemon@^2.0.2: - version "2.0.15" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e" - integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA== - dependencies: - chokidar "^3.5.2" - debug "^3.2.7" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.8" - semver "^5.7.1" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.5" - update-notifier "^5.1.0" - nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -9480,23 +9249,6 @@ nopt@^5.0.0: dependencies: abbrev "1" -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -9529,7 +9281,7 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -normalize-url@^4.1.0, normalize-url@^4.5.0: +normalize-url@^4.5.0: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== @@ -9624,21 +9376,6 @@ npm-registry-fetch@^9.0.0: minizlib "^2.0.0" npm-package-arg "^8.0.0" -npm-run-all@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" - integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== - dependencies: - ansi-styles "^3.2.1" - chalk "^2.4.1" - cross-spawn "^6.0.5" - memorystream "^0.3.1" - minimatch "^3.0.4" - pidtree "^0.3.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -9937,11 +9674,6 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-event@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" @@ -10028,16 +9760,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - pacote@11.2.4: version "11.2.4" resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.2.4.tgz#dc7ca740a573ed86a3bf863511d22c1d413ec82f" @@ -10236,13 +9958,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -10294,21 +10009,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== -pidtree@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" - integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== - pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -11362,11 +11067,6 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - pretty-bytes@^5.3.0, pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -11487,11 +11187,6 @@ psl@^1.1.28, psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -pstree.remy@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -11544,13 +11239,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -pupa@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - q@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" @@ -11680,16 +11368,6 @@ raw-loader@4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - re-reselect@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-4.0.0.tgz#9ddec4c72c4d952f68caa5aa4b76a9ed38b75cac" @@ -11926,15 +11604,6 @@ read-package-json-fast@^2.0.1: json-parse-even-better-errors "^2.3.0" npm-normalize-package-bin "^1.0.1" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -12076,20 +11745,6 @@ regexpu-core@^4.7.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -registry-auth-token@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - regjsgen@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" @@ -12260,7 +11915,7 @@ resolve@1.19.0: is-core-module "^2.1.0" path-parse "^1.0.6" -resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.9.0: +resolve@^1.1.7, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -12276,13 +11931,6 @@ resp-modifier@6.0.2: debug "^2.2.0" minimatch "^3.0.2" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -12641,13 +12289,6 @@ selfsigned@^1.10.11, selfsigned@^1.10.7, selfsigned@^1.10.8: dependencies: node-forge "^0.10.0" -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - semver-dsl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0" @@ -12662,11 +12303,6 @@ semver-intersect@1.4.0: dependencies: semver "^5.0.0" -"semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -12684,7 +12320,12 @@ semver@7.3.4: dependencies: lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^5.0.0, semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -12875,11 +12516,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" - integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -13221,32 +12857,6 @@ sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== - spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -13450,7 +13060,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13468,15 +13078,6 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string.prototype.padend@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz#997a6de12c92c7cb34dc8a201a6c53d9bd88a5f1" - integrity sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -13542,11 +13143,6 @@ strip-ansi@^7.0.0: dependencies: ansi-regex "^6.0.1" -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -13557,11 +13153,6 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - style-loader@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" @@ -13634,7 +13225,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.3.0, supports-color@^5.4.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -13938,11 +13529,6 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -13988,13 +13574,6 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -14143,11 +13722,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -14171,13 +13745,6 @@ type@^2.5.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -14242,11 +13809,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -undefsafe@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - unfetch@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" @@ -14309,13 +13871,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - universal-analytics@0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.23.tgz#d915e676850c25c4156762471bdd7cf2eaaca8ac" @@ -14363,26 +13918,6 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" - integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== - dependencies: - boxen "^5.0.0" - chalk "^4.1.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.4.0" - is-npm "^5.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.1.0" - pupa "^2.1.1" - semver "^7.3.4" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -14395,13 +13930,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - url-parse@^1.4.3, url-parse@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" @@ -14490,14 +14018,6 @@ uuid@^3.0.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - validate-npm-package-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" @@ -15045,13 +14565,6 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -15108,16 +14621,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - ws@^6.2.1: version "6.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" @@ -15140,11 +14643,6 @@ ws@~7.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - xhr2@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.1.tgz#4e73adc4f9cfec9cbd2157f73efdce3a5f108a93" From 33488ccf40052ee57a3d24ff438c818cfe7dd8ae Mon Sep 17 00:00:00 2001 From: William Welling Date: Mon, 6 Dec 2021 16:54:55 -0600 Subject: [PATCH 22/59] extend environment and use injected app config --- .github/workflows/build.yml | 2 +- angular.json | 26 - nodemon.json | 6 + package.json | 16 +- scripts/serve.ts | 14 +- scripts/test-rest.ts | 16 +- server.ts | 31 +- src/app/app.component.spec.ts | 3 + src/app/app.component.ts | 13 +- src/app/app.module.ts | 24 +- .../item-types/shared/item.component.ts | 3 +- src/app/root/root.component.ts | 3 +- src/config/app-config.interface.ts | 6 +- src/config/config.server.ts | 7 +- src/config/default-app-config.ts | 6 +- src/environments/environment.ci.ts | 294 ------- src/environments/environment.prod.ts | 284 +------ src/environments/environment.test.ts | 2 +- src/environments/environment.ts | 285 +------ src/main.browser.ts | 40 +- src/modules/app/browser-app.module.ts | 35 +- src/modules/app/server-app.module.ts | 28 +- tslint.json | 4 - webpack/webpack.browser.ts | 6 +- yarn.lock | 791 +++++++++++++----- 25 files changed, 742 insertions(+), 1203 deletions(-) create mode 100644 nodemon.json delete mode 100644 src/environments/environment.ci.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ab537f80cb..7758020724 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: run: yarn run lint - name: Run build - run: yarn run build:ssr:ci + run: yarn run build:prod - name: Run specs (unit tests) run: yarn run test:headless diff --git a/angular.json b/angular.json index b81d166cea..b9b8cb41cc 100644 --- a/angular.json +++ b/angular.json @@ -94,22 +94,6 @@ "maximumError": "300kb" } ] - }, - "ci": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.ci.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "extractCss": true, - "namedChunks": false, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true } } }, @@ -222,16 +206,6 @@ "with": "src/environments/environment.prod.ts" } ] - }, - "ci": { - "sourceMap": false, - "optimization": true, - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.ci.ts" - } - ] } } }, diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000000..dec8d9724c --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": [ + "config" + ], + "ext": "json" +} diff --git a/package.json b/package.json index 4ce674ce1c..e0b07b1040 100644 --- a/package.json +++ b/package.json @@ -3,24 +3,25 @@ "version": "0.0.0", "scripts": { "ng": "ng", + "config:watch": "nodemon", "test:rest": "ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts", "start": "yarn run start:prod", - "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts", - "start:dev": "yarn run serve", + "start:dev": "nodemon --exec \"yarn run serve\"", "start:prod": "yarn run build:prod && yarn run serve:ssr", "start:mirador:prod": "yarn run build:mirador && yarn run start:prod", + "serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts", + "serve:ssr": "node dist/server/main", "analyze": "webpack-bundle-analyzer dist/browser/stats.json", "build": "ng build", "build:stats": "ng build --stats-json", "build:prod": "yarn run build:ssr", "build:ssr": "ng build --configuration production && ng run dspace-angular:server:production", - "build:ssr:ci": "ng build --configuration ci && ng run dspace-angular:server:ci", - "test": "ng test --sourceMap=true --watch=true --configuration test", + "test": "ng test --sourceMap=true --watch=false --configuration test", + "test:watch": "nodemon --exec \"ng test --sourceMap=true --watch=true --configuration test\"", "test:headless": "ng test --sourceMap=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage", "lint": "ng lint", "lint-fix": "ng lint --fix=true", "e2e": "ng e2e", - "serve:ssr": "node dist/server/main", "clean:dev:config": "rimraf src/assets/appConfig.json", "clean:coverage": "rimraf coverage", "clean:dist": "rimraf dist", @@ -86,8 +87,8 @@ "file-saver": "^2.0.5", "filesize": "^6.1.0", "font-awesome": "4.7.0", - "https": "1.0.0", "http-proxy-middleware": "^1.0.5", + "https": "1.0.0", "js-cookie": "2.2.1", "json5": "^2.1.3", "jsonschema": "1.4.0", @@ -162,6 +163,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "karma-mocha-reporter": "2.2.5", + "nodemon": "^2.0.15", "optimize-css-assets-webpack-plugin": "^5.0.4", "postcss-apply": "0.11.0", "postcss-import": "^12.0.1", @@ -186,4 +188,4 @@ "webpack-cli": "^4.2.0", "webpack-dev-server": "^4.5.0" } -} \ No newline at end of file +} diff --git a/scripts/serve.ts b/scripts/serve.ts index 903374c615..bf5506b8bd 100644 --- a/scripts/serve.ts +++ b/scripts/serve.ts @@ -1,16 +1,14 @@ import * as child from 'child_process'; -import { environment } from '../src/environments/environment'; +import { AppConfig } from '../src/config/app-config.interface'; +import { buildAppConfig } from '../src/config/config.server'; -// import { AppConfig } from '../src/config/app-config.interface'; -// import { buildAppConfig } from '../src/config/config.server'; - -// const appConfig: AppConfig = buildAppConfig(); +const appConfig: AppConfig = buildAppConfig(); /** - * Calls `ng serve` with the following arguments configured for the UI in the environment: host, port, nameSpace, ssl + * Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl */ child.spawn( - `ng serve --host ${environment.ui.host} --port ${environment.ui.port} --serve-path ${environment.ui.nameSpace} --ssl ${environment.ui.ssl}`, - { stdio:'inherit', shell: true } + `ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl}`, + { stdio: 'inherit', shell: true } ); diff --git a/scripts/test-rest.ts b/scripts/test-rest.ts index 1584613387..00455d69b5 100644 --- a/scripts/test-rest.ts +++ b/scripts/test-rest.ts @@ -1,15 +1,13 @@ import * as http from 'http'; import * as https from 'https'; -import { environment } from '../src/environments/environment'; - -// import { AppConfig } from '../src/config/app-config.interface'; -// import { buildAppConfig } from '../src/config/config.server'; +import { AppConfig } from '../src/config/app-config.interface'; +import { buildAppConfig } from '../src/config/config.server'; -// const appConfig: AppConfig = buildAppConfig(); +const appConfig: AppConfig = buildAppConfig(); /** - * Script to test the connection with the configured REST API (in the 'rest' settings of your environment.*.ts) + * Script to test the connection with the configured REST API (in the 'rest' settings of your appConfig.*.json) * * This script is useful to test for any Node.js connection issues with your REST API. * @@ -17,11 +15,11 @@ import { environment } from '../src/environments/environment'; */ // Get root URL of configured REST API -const restUrl = environment.rest.baseUrl + '/api'; +const restUrl = appConfig.rest.baseUrl + '/api'; console.log(`...Testing connection to REST API at ${restUrl}...\n`); // If SSL enabled, test via HTTPS, else via HTTP -if (environment.rest.ssl) { +if (appConfig.rest.ssl) { const req = https.request(restUrl, (res) => { console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`); res.on('data', (data) => { @@ -61,7 +59,7 @@ function checkJSONResponse(responseData: any): any { console.log(`\t"dspaceVersion" = ${parsedData.dspaceVersion}`); console.log(`\t"dspaceUI" = ${parsedData.dspaceUI}`); console.log(`\t"dspaceServer" = ${parsedData.dspaceServer}`); - console.log(`\t"dspaceServer" property matches UI's "rest" config? ${(parsedData.dspaceServer === environment.rest.baseUrl)}`); + console.log(`\t"dspaceServer" property matches UI's "rest" config? ${(parsedData.dspaceServer === appConfig.rest.baseUrl)}`); // Check for "authn" and "sites" in "_links" section as they should always exist (even if no data)! const linksFound: string[] = Object.keys(parsedData._links); console.log(`\tDoes "/api" endpoint have HAL links ("_links" section)? ${linksFound.includes('authn') && linksFound.includes('sites')}`); diff --git a/server.ts b/server.ts index 9a48a91ffb..70d01e7710 100644 --- a/server.ts +++ b/server.ts @@ -19,29 +19,33 @@ import 'zone.js/dist/zone-node'; import 'reflect-metadata'; import 'rxjs'; -import * as fs from 'fs'; import * as pem from 'pem'; import * as https from 'https'; import * as morgan from 'morgan'; import * as express from 'express'; import * as bodyParser from 'body-parser'; import * as compression from 'compression'; + +import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; +import { APP_BASE_HREF } from '@angular/common'; import { enableProdMode } from '@angular/core'; -import { existsSync } from 'fs'; + import { ngExpressEngine } from '@nguniversal/express-engine'; import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; + import { environment } from './src/environments/environment'; import { createProxyMiddleware } from 'http-proxy-middleware'; import { hasValue, hasNoValue } from './src/app/shared/empty.util'; -import { APP_BASE_HREF } from '@angular/common'; + import { UIServerConfig } from './src/config/ui-server-config.interface'; import { ServerAppModule } from './src/main.server'; -// import { buildAppConfig } from './src/config/config.server'; -// import { AppConfig, APP_CONFIG } from './src/config/app-config.interface'; +import { buildAppConfig } from './src/config/config.server'; +import { AppConfig, APP_CONFIG } from './src/config/app-config.interface'; +import { extendEnvironmentWithAppConfig } from './src/config/config.util'; /* * Set path for the browser application's dist folder @@ -54,7 +58,10 @@ const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : ' const cookieParser = require('cookie-parser'); -// const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/appConfig.json')); +const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/appConfig.json')); + +// extend environment with app config for server +extendEnvironmentWithAppConfig(environment, appConfig); // The Express app is exported so that it can be used by serverless Functions. export function app() { @@ -105,10 +112,10 @@ export function app() { provide: RESPONSE, useValue: (options as any).req.res, }, - // { - // provide: APP_CONFIG, - // useValue: appConfig - // } + { + provide: APP_CONFIG, + useValue: environment + } ] })(_, (options as any), callback) ); @@ -246,14 +253,14 @@ function start() { if (environment.ui.ssl) { let serviceKey; try { - serviceKey = fs.readFileSync('./config/ssl/key.pem'); + serviceKey = readFileSync('./config/ssl/key.pem'); } catch (e) { console.warn('Service key not found at ./config/ssl/key.pem'); } let certificate; try { - certificate = fs.readFileSync('./config/ssl/cert.pem'); + certificate = readFileSync('./config/ssl/cert.pem'); } catch (e) { console.warn('Certificate not found at ./config/ssl/key.pem'); } diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 3f2dc45ce7..d33bade8c2 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -36,6 +36,8 @@ import { GoogleAnalyticsService } from './statistics/google-analytics.service'; import { ThemeService } from './shared/theme-support/theme.service'; import { getMockThemeService } from './shared/mocks/theme-service.mock'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; +import { APP_CONFIG } from '../config/app-config.interface'; +import { environment } from '../environments/environment'; let comp: AppComponent; let fixture: ComponentFixture; @@ -83,6 +85,7 @@ describe('App component', () => { { provide: LocaleService, useValue: getMockLocaleService() }, { provide: ThemeService, useValue: getMockThemeService() }, { provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy }, + { provide: APP_CONFIG, useValue: environment }, provideMockStore({ initialState }), AppComponent, RouteService diff --git a/src/app/app.component.ts b/src/app/app.component.ts index bf096c210f..2a7a885794 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -18,6 +18,7 @@ import { Router, } from '@angular/router'; +import { isEqual } from 'lodash'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { select, Store } from '@ngrx/store'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -46,6 +47,7 @@ import { BASE_THEME_NAME } from './shared/theme-support/theme.constants'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; +import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; @Component({ selector: 'ds-app', @@ -59,7 +61,7 @@ export class AppComponent implements OnInit, AfterViewInit { collapsedSidebarWidth: Observable; totalSidebarWidth: Observable; theme: Observable = of({} as any); - notificationOptions = environment.notifications; + notificationOptions; models; /** @@ -88,6 +90,7 @@ export class AppComponent implements OnInit, AfterViewInit { @Inject(NativeWindowService) private _window: NativeWindowRef, @Inject(DOCUMENT) private document: any, @Inject(PLATFORM_ID) private platformId: any, + @Inject(APP_CONFIG) private appConfig: AppConfig, private themeService: ThemeService, private translate: TranslateService, private store: Store, @@ -106,6 +109,14 @@ export class AppComponent implements OnInit, AfterViewInit { @Optional() private googleAnalyticsService: GoogleAnalyticsService, ) { + console.log(this.appConfig); + + if (!isEqual(environment, this.appConfig)) { + throw new Error('environment does not match app config!'); + } + + this.notificationOptions = environment.notifications; + /* Use models object so all decorators are actually called */ this.models = models; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a1c5addae8..98b2d9fe92 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -58,14 +58,18 @@ import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { UUIDService } from './core/shared/uuid.service'; import { CookieService } from './core/services/cookie.service'; -// import { AppConfig, APP_CONFIG } from '../config/app-config.interface'; +import { AppConfig, APP_CONFIG } from '../config/app-config.interface'; -export function getBase() { - return environment.ui.nameSpace; +export function getConfig() { + return environment; } -export function getMetaReducers(): MetaReducer[] { - return environment.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers; +export function getBase(appConfig: AppConfig) { + return appConfig.ui.nameSpace; +} + +export function getMetaReducers(appConfig: AppConfig): MetaReducer[] { + return appConfig.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers; } /** @@ -100,15 +104,19 @@ IMPORTS.push( ); const PROVIDERS = [ + { + provide: APP_CONFIG, + useFactory: getConfig + }, { provide: APP_BASE_HREF, useFactory: getBase, - // deps: [APP_CONFIG] + deps: [APP_CONFIG] }, { provide: USER_PROVIDED_META_REDUCERS, useFactory: getMetaReducers, - // deps: [APP_CONFIG] + deps: [APP_CONFIG] }, { provide: RouterStateSerializer, @@ -199,7 +207,7 @@ const EXPORTS = [ @NgModule({ imports: [ - BrowserModule.withServerTransition({ appId: 'serverApp' }), + BrowserModule.withServerTransition({ appId: 'dspace-angular' }), ...IMPORTS ], providers: [ diff --git a/src/app/item-page/simple/item-types/shared/item.component.ts b/src/app/item-page/simple/item-types/shared/item.component.ts index fa7fba8724..71b0b5b678 100644 --- a/src/app/item-page/simple/item-types/shared/item.component.ts +++ b/src/app/item-page/simple/item-types/shared/item.component.ts @@ -36,9 +36,10 @@ export class ItemComponent implements OnInit { */ iiifQuery$: Observable; - mediaViewer = environment.mediaViewer; + mediaViewer; constructor(protected routeService: RouteService) { + this.mediaViewer = environment.mediaViewer; } ngOnInit(): void { diff --git a/src/app/root/root.component.ts b/src/app/root/root.component.ts index 209f17b520..6ba859ef23 100644 --- a/src/app/root/root.component.ts +++ b/src/app/root/root.component.ts @@ -32,7 +32,7 @@ export class RootComponent implements OnInit { collapsedSidebarWidth: Observable; totalSidebarWidth: Observable; theme: Observable = of({} as any); - notificationOptions = environment.notifications; + notificationOptions; models; /** @@ -58,6 +58,7 @@ export class RootComponent implements OnInit { private menuService: MenuService, private windowService: HostWindowService ) { + this.notificationOptions = environment.notifications; } ngOnInit() { diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 9a993cb415..4c15e6dbc1 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -14,6 +14,7 @@ import { ThemeConfig } from './theme.model'; import { AuthConfig } from './auth-config.interfaces'; import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface'; +import { makeStateKey } from '@angular/platform-browser'; interface AppConfig extends Config { ui: UIServerConfig; @@ -37,7 +38,10 @@ interface AppConfig extends Config { const APP_CONFIG = new InjectionToken('APP_CONFIG'); +const APP_CONFIG_STATE = makeStateKey('APP_CONFIG_STATE'); + export { AppConfig, - APP_CONFIG + APP_CONFIG, + APP_CONFIG_STATE }; diff --git a/src/config/config.server.ts b/src/config/config.server.ts index 355ba028ab..51a8c8119f 100644 --- a/src/config/config.server.ts +++ b/src/config/config.server.ts @@ -2,13 +2,11 @@ import * as colors from 'colors'; import * as fs from 'fs'; import { join } from 'path'; -import { environment } from '../environments/environment'; - import { AppConfig } from './app-config.interface'; import { Config } from './config.interface'; import { DefaultAppConfig } from './default-app-config'; import { ServerConfig } from './server-config.interface'; -import { extendConfig, extendEnvironmentWithAppConfig } from './config.util'; +import { extendConfig } from './config.util'; import { isNotEmpty } from '../app/shared/empty.util'; const CONFIG_PATH = join(process.cwd(), 'config'); @@ -182,9 +180,6 @@ export const buildAppConfig = (destConfigPath?: string): AppConfig => { buildBaseUrl(appConfig.ui); buildBaseUrl(appConfig.rest); - // extend environment with app config for server side use - extendEnvironmentWithAppConfig(environment, appConfig); - if (isNotEmpty(destConfigPath)) { fs.writeFileSync(destConfigPath, JSON.stringify(appConfig, null, 2)); diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 12859db977..bceea29b96 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -18,7 +18,7 @@ import { UIServerConfig } from './ui-server-config.interface'; import { UniversalConfig } from './universal-config.interface'; export class DefaultAppConfig implements AppConfig { - production: boolean = false; + production = false; // Angular Universal server settings. // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. @@ -172,10 +172,10 @@ export class DefaultAppConfig implements AppConfig { }; // NOTE: will log all redux actions and transfers in console - debug: boolean = false; + debug = false; // Default Language in which the UI will be rendered if the user's browser language is not an active language - defaultLanguage: string = 'en'; + defaultLanguage = 'en'; // Languages. DSpace Angular holds a message catalog for each of the following languages. // When set to active, users will be able to switch to the use of this language in the user interface. diff --git a/src/environments/environment.ci.ts b/src/environments/environment.ci.ts deleted file mode 100644 index bb12eb14c1..0000000000 --- a/src/environments/environment.ci.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; -import { RestRequestMethod } from '../app/core/data/rest-request-method'; -import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; -import { AppConfig } from '../config/app-config.interface'; - -export const environment: AppConfig = { - production: true, - - // Angular Universal settings - universal: { - preboot: true, - async: true, - time: false - }, - - // Angular Universal server settings. - // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. - ui: { - ssl: false, - host: 'localhost', - port: 4000, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/', - baseUrl: 'http://localhost:4000/', - - // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). - rateLimiter: { - windowMs: 1 * 60 * 1000, // 1 minute - max: 500 // limit each IP to 500 requests per windowMs - } - }, - - // The REST API server settings. - // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. - rest: { - ssl: false, - host: 'localhost', - port: 8080, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server', - baseUrl: 'http://localhost:8080/server' - }, - - // Caching settings - cache: { - // NOTE: how long should objects be cached for by default - msToLive: { - default: 15 * 60 * 1000 // 15 minutes - }, - control: 'max-age=60', // revalidate browser - autoSync: { - defaultTime: 0, - maxBufferSize: 100, - timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds - } - }, - - // Authentication settings - auth: { - // Authentication UI settings - ui: { - // the amount of time before the idle warning is shown - timeUntilIdle: 15 * 60 * 1000, // 15 minutes - // the amount of time the user has to react after the idle warning is shown before they are logged out. - idleGracePeriod: 5 * 60 * 1000 // 5 minutes - }, - // Authentication REST settings - rest: { - // If the rest token expires in less than this amount of time, it will be refreshed automatically. - // This is independent from the idle warning. - timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes - } - }, - - // Form settings - form: { - // NOTE: Map server-side validators to comparative Angular form validators - validatorMap: { - required: 'required', - regex: 'pattern' - } - }, - - // Notifications - notifications: { - rtl: false, - position: ['top', 'right'], - maxStack: 8, - // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically - timeOut: 5000, // 5 second - clickToClose: true, - // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' - animate: NotificationAnimationsType.Scale - }, - - // Submission settings - submission: { - autosave: { - // NOTE: which metadata trigger an autosave - metadata: [], - /** - * NOTE: after how many time (milliseconds) submission is saved automatically - * eg. timer: 5 * (1000 * 60); // 5 minutes - */ - timer: 0 - }, - icons: { - metadata: [ - /** - * NOTE: example of configuration - * { - * // NOTE: metadata name - * name: 'dc.author', - * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used - * style: 'fa-user' - * } - */ - { - name: 'dc.author', - style: 'fas fa-user' - }, - // default configuration - { - name: 'default', - style: '' - } - ], - authority: { - confidence: [ - /** - * NOTE: example of configuration - * { - * // NOTE: confidence value - * value: 'dc.author', - * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used - * style: 'fa-user' - * } - */ - { - value: 600, - style: 'text-success' - }, - { - value: 500, - style: 'text-info' - }, - { - value: 400, - style: 'text-warning' - }, - // default configuration - { - value: 'default', - style: 'text-muted' - } - - ] - } - } - }, - - // NOTE: will log all redux actions and transfers in console - debug: false, - - // Default Language in which the UI will be rendered if the user's browser language is not an active language - defaultLanguage: 'en', - - // Languages. DSpace Angular holds a message catalog for each of the following languages. - // When set to active, users will be able to switch to the use of this language in the user interface. - languages: [ - { code: 'en', label: 'English', active: true }, - { code: 'cs', label: 'Čeština', active: true }, - { code: 'de', label: 'Deutsch', active: true }, - { code: 'es', label: 'Español', active: true }, - { code: 'fr', label: 'Français', active: true }, - { code: 'lv', label: 'Latviešu', active: true }, - { code: 'hu', label: 'Magyar', active: true }, - { code: 'nl', label: 'Nederlands', active: true }, - { code: 'pt-PT', label: 'Português', active: true }, - { code: 'pt-BR', label: 'Português do Brasil', active: true }, - { code: 'fi', label: 'Suomi', active: true } - ], - - // Browse-By Pages - browseBy: { - // Amount of years to display using jumps of one year (current year - oneYearLimit) - oneYearLimit: 10, - // Limit for years to display using jumps of five years (current year - fiveYearLimit) - fiveYearLimit: 30, - // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) - defaultLowerLimit: 1900, - // List of all the active Browse-By types - // Adding a type will activate their Browse-By page and add them to the global navigation menu, - // as well as community and collection pages - // Allowed fields and their purpose: - // id: The browse id to use for fetching info from the rest api - // type: The type of Browse-By page to display - // metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') - types: [ - { - id: 'title', - type: BrowseByType.Title, - }, - { - id: 'dateissued', - type: BrowseByType.Date, - metadataField: 'dc.date.issued' - }, - { - id: 'author', - type: BrowseByType.Metadata - }, - { - id: 'subject', - type: BrowseByType.Metadata - } - ] - }, - - // Item Page Config - item: { - edit: { - undoTimeout: 10000 // 10 seconds - } - }, - - // Collection Page Config - collection: { - edit: { - undoTimeout: 10000 // 10 seconds - } - }, - - // Theme Config - themes: [ - // Add additional themes here. In the case where multiple themes match a route, the first one - // in this list will get priority. It is advisable to always have a theme that matches - // every route as the last one - - // { - // // A theme with a handle property will match the community, collection or item with the given - // // handle, and all collections and/or items within it - // name: 'custom', - // handle: '10673/1233' - // }, - // { - // // A theme with a regex property will match the route using a regular expression. If it - // // matches the route for a community or collection it will also apply to all collections - // // and/or items within it - // name: 'custom', - // regex: 'collections\/e8043bc2.*' - // }, - // { - // // A theme with a uuid property will match the community, collection or item with the given - // // ID, and all collections and/or items within it - // name: 'custom', - // uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' - // }, - // { - // // The extends property specifies an ancestor theme (by name). Whenever a themed component is not found - // // in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. - // name: 'custom-A', - // extends: 'custom-B', - // // Any of the matching properties above can be used - // handle: '10673/34' - // }, - // { - // name: 'custom-B', - // extends: 'custom', - // handle: '10673/12' - // }, - // { - // // A theme with only a name will match every route - // name: 'custom' - // }, - // { - // // This theme will use the default bootstrap styling for DSpace components - // name: BASE_THEME_NAME - // }, - - { - // The default dspace theme - name: 'dspace' - } - ], - - // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). - // For images, this enables a gallery viewer where you can zoom or page through images. - // For videos, this enables embedded video streaming - mediaViewer: { - image: false, - video: false - } -}; diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 63a9e01a2e..85c0f8fddc 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,9 +1,6 @@ -import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; -import { RestRequestMethod } from '../app/core/data/rest-request-method'; -import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; import { AppConfig } from '../config/app-config.interface'; -export const environment: AppConfig = { +export const environment: Partial = { production: true, // Angular Universal settings @@ -11,284 +8,5 @@ export const environment: AppConfig = { preboot: true, async: true, time: false - }, - - // Angular Universal server settings. - // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. - ui: { - ssl: false, - host: 'localhost', - port: 4000, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/', - baseUrl: 'http://localhost:4000/', - - // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). - rateLimiter: { - windowMs: 1 * 60 * 1000, // 1 minute - max: 500 // limit each IP to 500 requests per windowMs - } - }, - - // The REST API server settings. - // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. - rest: { - ssl: true, - host: 'api7.dspace.org', - port: 443, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server', - baseUrl: 'https://api7.dspace.org/server' - }, - - // Caching settings - cache: { - // NOTE: how long should objects be cached for by default - msToLive: { - default: 15 * 60 * 1000 // 15 minutes - }, - control: 'max-age=60', // revalidate browser - autoSync: { - defaultTime: 0, - maxBufferSize: 100, - timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds - } - }, - - // Authentication settings - auth: { - // Authentication UI settings - ui: { - // the amount of time before the idle warning is shown - timeUntilIdle: 15 * 60 * 1000, // 15 minutes - // the amount of time the user has to react after the idle warning is shown before they are logged out. - idleGracePeriod: 5 * 60 * 1000 // 5 minutes - }, - // Authentication REST settings - rest: { - // If the rest token expires in less than this amount of time, it will be refreshed automatically. - // This is independent from the idle warning. - timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes - } - }, - - // Form settings - form: { - // NOTE: Map server-side validators to comparative Angular form validators - validatorMap: { - required: 'required', - regex: 'pattern' - } - }, - - // Notifications - notifications: { - rtl: false, - position: ['top', 'right'], - maxStack: 8, - // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically - timeOut: 5000, // 5 second - clickToClose: true, - // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' - animate: NotificationAnimationsType.Scale - }, - - // Submission settings - submission: { - autosave: { - // NOTE: which metadata trigger an autosave - metadata: [], - /** - * NOTE: after how many time (milliseconds) submission is saved automatically - * eg. timer: 5 * (1000 * 60); // 5 minutes - */ - timer: 0 - }, - icons: { - metadata: [ - /** - * NOTE: example of configuration - * { - * // NOTE: metadata name - * name: 'dc.author', - * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used - * style: 'fa-user' - * } - */ - { - name: 'dc.author', - style: 'fas fa-user' - }, - // default configuration - { - name: 'default', - style: '' - } - ], - authority: { - confidence: [ - /** - * NOTE: example of configuration - * { - * // NOTE: confidence value - * value: 'dc.author', - * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used - * style: 'fa-user' - * } - */ - { - value: 600, - style: 'text-success' - }, - { - value: 500, - style: 'text-info' - }, - { - value: 400, - style: 'text-warning' - }, - // default configuration - { - value: 'default', - style: 'text-muted' - } - - ] - } - } - }, - - // NOTE: will log all redux actions and transfers in console - debug: false, - - // Default Language in which the UI will be rendered if the user's browser language is not an active language - defaultLanguage: 'en', - - // Languages. DSpace Angular holds a message catalog for each of the following languages. - // When set to active, users will be able to switch to the use of this language in the user interface. - languages: [ - { code: 'en', label: 'English', active: true }, - { code: 'cs', label: 'Čeština', active: true }, - { code: 'de', label: 'Deutsch', active: true }, - { code: 'es', label: 'Español', active: true }, - { code: 'fr', label: 'Français', active: true }, - { code: 'lv', label: 'Latviešu', active: true }, - { code: 'hu', label: 'Magyar', active: true }, - { code: 'nl', label: 'Nederlands', active: true }, - { code: 'pt-PT', label: 'Português', active: true }, - { code: 'pt-BR', label: 'Português do Brasil', active: true }, - { code: 'fi', label: 'Suomi', active: true } - ], - - // Browse-By Pages - browseBy: { - // Amount of years to display using jumps of one year (current year - oneYearLimit) - oneYearLimit: 10, - // Limit for years to display using jumps of five years (current year - fiveYearLimit) - fiveYearLimit: 30, - // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) - defaultLowerLimit: 1900, - // List of all the active Browse-By types - // Adding a type will activate their Browse-By page and add them to the global navigation menu, - // as well as community and collection pages - // Allowed fields and their purpose: - // id: The browse id to use for fetching info from the rest api - // type: The type of Browse-By page to display - // metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') - types: [ - { - id: 'title', - type: BrowseByType.Title, - }, - { - id: 'dateissued', - type: BrowseByType.Date, - metadataField: 'dc.date.issued' - }, - { - id: 'author', - type: BrowseByType.Metadata - }, - { - id: 'subject', - type: BrowseByType.Metadata - } - ] - }, - - // Item Page Config - item: { - edit: { - undoTimeout: 10000 // 10 seconds - } - }, - - // Collection Page Config - collection: { - edit: { - undoTimeout: 10000 // 10 seconds - } - }, - - // Theme Config - themes: [ - // Add additional themes here. In the case where multiple themes match a route, the first one - // in this list will get priority. It is advisable to always have a theme that matches - // every route as the last one - - // { - // // A theme with a handle property will match the community, collection or item with the given - // // handle, and all collections and/or items within it - // name: 'custom', - // handle: '10673/1233' - // }, - // { - // // A theme with a regex property will match the route using a regular expression. If it - // // matches the route for a community or collection it will also apply to all collections - // // and/or items within it - // name: 'custom', - // regex: 'collections\/e8043bc2.*' - // }, - // { - // // A theme with a uuid property will match the community, collection or item with the given - // // ID, and all collections and/or items within it - // name: 'custom', - // uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' - // }, - // { - // // The extends property specifies an ancestor theme (by name). Whenever a themed component is not found - // // in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. - // name: 'custom-A', - // extends: 'custom-B', - // // Any of the matching properties above can be used - // handle: '10673/34' - // }, - // { - // name: 'custom-B', - // extends: 'custom', - // handle: '10673/12' - // }, - // { - // // A theme with only a name will match every route - // name: 'custom' - // }, - // { - // // This theme will use the default bootstrap styling for DSpace components - // name: BASE_THEME_NAME - // }, - - { - // The default dspace theme - name: 'dspace' - } - ], - - // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). - // For images, this enables a gallery viewer where you can zoom or page through images. - // For videos, this enables embedded video streaming - mediaViewer: { - image: false, - video: false } }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 394a2e5dac..0af2fc8546 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -1,4 +1,4 @@ -// This configuration is only used for unit tests, end-to-end tests use environment.e2e.ts +// This configuration is only used for unit tests, end-to-end tests use environment.prod.ts import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; import { RestRequestMethod } from '../app/core/data/rest-request-method'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index ba7b262ac4..29b67704eb 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,15 +1,11 @@ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --configuration production` replaces `environment.ts` with `environment.prod.ts`. -// `ng build --configuration ci` replaces `environment.ts` with `environment.ci.ts`. // `ng test --configuration test` replaces `environment.ts` with `environment.test.ts`. // The list of file replacements can be found in `angular.json`. -import { BrowseByType } from '../app/browse-by/browse-by-switcher/browse-by-decorator'; -import { RestRequestMethod } from '../app/core/data/rest-request-method'; -import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; import { AppConfig } from '../config/app-config.interface'; -export const environment: AppConfig = { +export const environment: Partial = { production: false, // Angular Universal settings @@ -17,285 +13,6 @@ export const environment: AppConfig = { preboot: true, async: true, time: false - }, - - // Angular Universal server settings. - // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. - ui: { - ssl: false, - host: 'localhost', - port: 4000, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/', - baseUrl: 'http://localhost:4000/', - - // The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). - rateLimiter: { - windowMs: 1 * 60 * 1000, // 1 minute - max: 500 // limit each IP to 500 requests per windowMs - } - }, - - // The REST API server settings. - // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. - rest: { - ssl: true, - host: 'api7.dspace.org', - port: 443, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server', - baseUrl: 'https://api7.dspace.org/server' - }, - - // Caching settings - cache: { - // NOTE: how long should objects be cached for by default - msToLive: { - default: 15 * 60 * 1000 // 15 minutes - }, - control: 'max-age=60', // revalidate browser - autoSync: { - defaultTime: 0, - maxBufferSize: 100, - timePerMethod: { [RestRequestMethod.PATCH]: 3 } as any // time in seconds - } - }, - - // Authentication settings - auth: { - // Authentication UI settings - ui: { - // the amount of time before the idle warning is shown - timeUntilIdle: 15 * 60 * 1000, // 15 minutes - // the amount of time the user has to react after the idle warning is shown before they are logged out. - idleGracePeriod: 5 * 60 * 1000 // 5 minutes - }, - // Authentication REST settings - rest: { - // If the rest token expires in less than this amount of time, it will be refreshed automatically. - // This is independent from the idle warning. - timeLeftBeforeTokenRefresh: 2 * 60 * 1000 // 2 minutes - } - }, - - // Form settings - form: { - // NOTE: Map server-side validators to comparative Angular form validators - validatorMap: { - required: 'required', - regex: 'pattern' - } - }, - - // Notifications - notifications: { - rtl: false, - position: ['top', 'right'], - maxStack: 8, - // NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically - timeOut: 5000, // 5 second - clickToClose: true, - // NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' - animate: NotificationAnimationsType.Scale - }, - - // Submission settings - submission: { - autosave: { - // NOTE: which metadata trigger an autosave - metadata: [], - /** - * NOTE: after how many time (milliseconds) submission is saved automatically - * eg. timer: 5 * (1000 * 60); // 5 minutes - */ - timer: 0 - }, - icons: { - metadata: [ - /** - * NOTE: example of configuration - * { - * // NOTE: metadata name - * name: 'dc.author', - * // NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used - * style: 'fa-user' - * } - */ - { - name: 'dc.author', - style: 'fas fa-user' - }, - // default configuration - { - name: 'default', - style: '' - } - ], - authority: { - confidence: [ - /** - * NOTE: example of configuration - * { - * // NOTE: confidence value - * value: 'dc.author', - * // NOTE: fontawesome (v4.x) icon classes and bootstrap utility classes can be used - * style: 'fa-user' - * } - */ - { - value: 600, - style: 'text-success' - }, - { - value: 500, - style: 'text-info' - }, - { - value: 400, - style: 'text-warning' - }, - // default configuration - { - value: 'default', - style: 'text-muted' - } - - ] - } - } - }, - - // NOTE: will log all redux actions and transfers in console - debug: false, - - // Default Language in which the UI will be rendered if the user's browser language is not an active language - defaultLanguage: 'en', - - // Languages. DSpace Angular holds a message catalog for each of the following languages. - // When set to active, users will be able to switch to the use of this language in the user interface. - languages: [ - { code: 'en', label: 'English', active: true }, - { code: 'cs', label: 'Čeština', active: true }, - { code: 'de', label: 'Deutsch', active: true }, - { code: 'es', label: 'Español', active: true }, - { code: 'fr', label: 'Français', active: true }, - { code: 'lv', label: 'Latviešu', active: true }, - { code: 'hu', label: 'Magyar', active: true }, - { code: 'nl', label: 'Nederlands', active: true }, - { code: 'pt-PT', label: 'Português', active: true }, - { code: 'pt-BR', label: 'Português do Brasil', active: true }, - { code: 'fi', label: 'Suomi', active: true } - ], - - // Browse-By Pages - browseBy: { - // Amount of years to display using jumps of one year (current year - oneYearLimit) - oneYearLimit: 10, - // Limit for years to display using jumps of five years (current year - fiveYearLimit) - fiveYearLimit: 30, - // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) - defaultLowerLimit: 1900, - // List of all the active Browse-By types - // Adding a type will activate their Browse-By page and add them to the global navigation menu, - // as well as community and collection pages - // Allowed fields and their purpose: - // id: The browse id to use for fetching info from the rest api - // type: The type of Browse-By page to display - // metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') - types: [ - { - id: 'title', - type: BrowseByType.Title, - }, - { - id: 'dateissued', - type: BrowseByType.Date, - metadataField: 'dc.date.issued' - }, - { - id: 'author', - type: BrowseByType.Metadata - }, - { - id: 'subject', - type: BrowseByType.Metadata - } - ] - }, - - // Item Page Config - item: { - edit: { - undoTimeout: 10000 // 10 seconds - } - }, - - // Collection Page Config - collection: { - edit: { - undoTimeout: 10000 // 10 seconds - } - }, - - // Theme Config - themes: [ - // Add additional themes here. In the case where multiple themes match a route, the first one - // in this list will get priority. It is advisable to always have a theme that matches - // every route as the last one - - // { - // // A theme with a handle property will match the community, collection or item with the given - // // handle, and all collections and/or items within it - // name: 'custom', - // handle: '10673/1233' - // }, - // { - // // A theme with a regex property will match the route using a regular expression. If it - // // matches the route for a community or collection it will also apply to all collections - // // and/or items within it - // name: 'custom', - // regex: 'collections\/e8043bc2.*' - // }, - // { - // // A theme with a uuid property will match the community, collection or item with the given - // // ID, and all collections and/or items within it - // name: 'custom', - // uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' - // }, - // { - // // The extends property specifies an ancestor theme (by name). Whenever a themed component is not found - // // in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. - // name: 'custom-A', - // extends: 'custom-B', - // // Any of the matching properties above can be used - // handle: '10673/34' - // }, - // { - // name: 'custom-B', - // extends: 'custom', - // handle: '10673/12' - // }, - // { - // // A theme with only a name will match every route - // name: 'custom' - // }, - // { - // // This theme will use the default bootstrap styling for DSpace components - // name: BASE_THEME_NAME - // }, - - { - // The default dspace theme - name: 'dspace' - } - ], - - // Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). - // For images, this enables a gallery viewer where you can zoom or page through images. - // For videos, this enables embedded video streaming - mediaViewer: { - image: false, - video: false } }; diff --git a/src/main.browser.ts b/src/main.browser.ts index 5a17c3e7cc..c72ca0cf31 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -11,13 +11,13 @@ import { hasValue } from './app/shared/empty.util'; import { BrowserAppModule } from './modules/app/browser-app.module'; import { environment } from './environments/environment'; +import { AppConfig } from './config/app-config.interface'; +import { extendEnvironmentWithAppConfig } from './config/config.util'; -// import { AppConfig, APP_CONFIG } from './config/app-config.interface'; -// import { extendEnvironmentWithAppConfig } from './config/config.util'; - -if (environment.production) { - enableProdMode(); -} +const bootstrap = () => platformBrowserDynamic() + .bootstrapModule(BrowserAppModule, { + preserveWhitespaces: true + }); const main = () => { // Load fonts async @@ -28,10 +28,22 @@ const main = () => { } }); - return platformBrowserDynamic() - .bootstrapModule(BrowserAppModule, { - preserveWhitespaces: true - }); + if (environment.production) { + enableProdMode(); + + return bootstrap(); + } else { + + return fetch('assets/appConfig.json') + .then((response) => response.json()) + .then((appConfig: AppConfig) => { + + // extend environment with app config for browser when not prerendered + extendEnvironmentWithAppConfig(environment, appConfig); + + return bootstrap(); + }); + } }; // support async tag or hmr @@ -40,11 +52,3 @@ if (hasValue(environment.universal) && environment.universal.preboot === false) } else { document.addEventListener('DOMContentLoaded', main); } - - -// fetch('assets/appConfig.json') -// .then((response) => response.json()) -// .then((appConfig: AppConfig) => { -// // extend environment with app config for client side use -// extendEnvironmentWithAppConfig(environment, appConfig); -// }); diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index efdaf12a1b..86497d4599 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -1,7 +1,8 @@ import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { BrowserModule, makeStateKey, TransferState } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterModule, NoPreloading } from '@angular/router'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -30,9 +31,13 @@ import { } from '../../app/core/services/browser-hard-redirect.service'; import { LocaleService } from '../../app/core/locale/locale.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; -import { RouterModule, NoPreloading } from '@angular/router'; import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { BrowserAuthRequestService } from '../../app/core/auth/browser-auth-request.service'; +import { AppConfig, APP_CONFIG_STATE } from '../../config/app-config.interface'; +import { DefaultAppConfig } from '../../config/default-app-config'; +import { extendEnvironmentWithAppConfig } from '../../config/config.util'; + +import { environment } from '../../environments/environment'; export const REQ_KEY = makeStateKey('req'); @@ -54,12 +59,12 @@ export function getRequest(transferState: TransferState): any { // forRoot ensures the providers are only created once IdlePreloadModule.forRoot(), RouterModule.forRoot([], { - // enableTracing: true, - useHash: false, - scrollPositionRestoration: 'enabled', - anchorScrolling: 'enabled', - preloadingStrategy: NoPreloading -}), + // enableTracing: true, + useHash: false, + scrollPositionRestoration: 'enabled', + anchorScrolling: 'enabled', + preloadingStrategy: NoPreloading + }), StatisticsModule.forRoot(), Angulartics2RouterlessModule.forRoot(), BrowserAnimationsModule, @@ -74,6 +79,20 @@ export function getRequest(transferState: TransferState): any { AppModule ], providers: [ + { + provide: APP_INITIALIZER, + useFactory: (transferState: TransferState) => { + if (transferState.hasKey(APP_CONFIG_STATE)) { + const appConfig = transferState.get(APP_CONFIG_STATE, new DefaultAppConfig()); + // extend environment with app config for browser + extendEnvironmentWithAppConfig(environment, appConfig); + } + + return () => true; + }, + deps: [TransferState], + multi: true + }, { provide: REQUEST, useFactory: getRequest, diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index dad3a60d5c..8c49554de9 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -1,38 +1,40 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { BrowserModule, TransferState } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ServerModule } from '@angular/platform-server'; +import { RouterModule } from '@angular/router'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { Angulartics2 } from 'angulartics2'; +import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; + import { AppComponent } from '../../app/app.component'; import { AppModule } from '../../app/app.module'; import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server-transfer-state.module'; import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; - import { TranslateJson5UniversalLoader } from '../../ngx-translate-loaders/translate-json5-universal.loader'; import { CookieService } from '../../app/core/services/cookie.service'; import { ServerCookieService } from '../../app/core/services/server-cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; import { ServerAuthService } from '../../app/core/auth/server-auth.service'; - -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { AngularticsProviderMock } from '../../app/shared/mocks/angulartics-provider.service.mock'; import { SubmissionService } from '../../app/submission/submission.service'; import { ServerSubmissionService } from '../../app/submission/server-submission.service'; import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; import { ServerLocaleService } from '../../app/core/locale/server-locale.service'; import { LocaleService } from '../../app/core/locale/locale.service'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { ForwardClientIpInterceptor } from '../../app/core/forward-client-ip/forward-client-ip.interceptor'; import { HardRedirectService } from '../../app/core/services/hard-redirect.service'; import { ServerHardRedirectService } from '../../app/core/services/server-hard-redirect.service'; -import { Angulartics2 } from 'angulartics2'; import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mock'; -import { RouterModule } from '@angular/router'; import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service'; +import { AppConfig, APP_CONFIG_STATE } from '../../config/app-config.interface'; + +import { environment } from '../../environments/environment'; export function createTranslateLoader() { return new TranslateJson5UniversalLoader('dist/server/assets/i18n/', '.json5'); @@ -60,6 +62,16 @@ export function createTranslateLoader() { AppModule ], providers: [ + // Initialize app config and extend environment + { + provide: APP_INITIALIZER, + useFactory: (transferState: TransferState) => { + transferState.set(APP_CONFIG_STATE, environment as AppConfig); + return () => true; + }, + deps: [TransferState], + multi: true + }, { provide: Angulartics2, useClass: Angulartics2Mock diff --git a/tslint.json b/tslint.json index 36a32ed795..ef85c50002 100644 --- a/tslint.json +++ b/tslint.json @@ -170,9 +170,5 @@ "use-life-cycle-interface": false, "no-outputs-metadata-property": true, "use-pipe-transform-interface": true - // "rxjs-collapse-imports": true, - // "rxjs-pipeable-operators-only": true, - // "rxjs-no-static-observable-methods": true, - // "rxjs-proper-imports": true } } diff --git a/webpack/webpack.browser.ts b/webpack/webpack.browser.ts index 187262071e..134eecb7ed 100644 --- a/webpack/webpack.browser.ts +++ b/webpack/webpack.browser.ts @@ -1,6 +1,6 @@ -// import { join } from 'path'; +import { join } from 'path'; -// import { buildAppConfig } from '../src/config/config.server'; +import { buildAppConfig } from '../src/config/config.server'; import { commonExports } from './webpack.common'; module.exports = Object.assign({}, commonExports, { @@ -10,7 +10,7 @@ module.exports = Object.assign({}, commonExports, { }, devServer: { before(app, server) { - // buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); + buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); } } }); diff --git a/yarn.lock b/yarn.lock index e8e9b67c4c..db63c66040 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1452,9 +1452,9 @@ integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== "@cypress/request@^2.88.6": - version "2.88.7" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.7.tgz#386d960ab845a96953723348088525d5a75aaac4" - integrity sha512-FTULIP2rnDJvZDT9t6B4nSfYR40ue19tVmv3wUcY05R9/FPCoMl1nAPJkzWzBCo7ltVn5ThQTbxiMoGBN7k0ig== + version "2.88.10" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" + integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1463,8 +1463,7 @@ extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" + http-signature "~1.3.6" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" @@ -1502,9 +1501,9 @@ integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== "@discoveryjs/json-ext@^0.5.0": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" - integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== + version "0.5.6" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" + integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== "@edsilv/http-status-codes@^1.0.3": version "1.0.3" @@ -1527,9 +1526,9 @@ integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== "@iiif/vocabulary@^1.0.20": - version "1.0.21" - resolved "https://registry.yarnpkg.com/@iiif/vocabulary/-/vocabulary-1.0.21.tgz#5808f62da11b64ca42a895025844be088df9f48a" - integrity sha512-XUD8RYeBiEzv8rdpC9tNBaQ0FMZuGWIkPsmcFhnqXW+5WjZmRLhgqycuoGwIjxt9nPsDgW1IxuiWduNgbLl9UA== + version "1.0.22" + resolved "https://registry.yarnpkg.com/@iiif/vocabulary/-/vocabulary-1.0.22.tgz#743d6cb1914e090137c56a4e44decbe7f8b21bdc" + integrity sha512-KOIZgRpDERMK4YGeSwPBNjpJETapMWp8pvEvSGUQkuTlqkiNLirZH65CIcEENZEL5A6mxrYLJX6okDYq7W1+RA== "@istanbuljs/schema@^0.1.2": version "0.1.3" @@ -1936,6 +1935,18 @@ semver "7.3.4" semver-intersect "1.4.0" +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -1972,9 +1983,9 @@ integrity sha512-M6x29Vk4681dght4IMnPIcF1SNmeEm0c4uatlTFhp+++H1oDK1THEIzuCC2WeCBVhX+gU0NndsseDS3zaCtlcQ== "@types/express-serve-static-core@^4.17.18": - version "4.17.25" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.25.tgz#e42f7046adc65ece2eb6059b77aecfbe9e9f82e0" - integrity sha512-OUJIVfRMFijZukGGwTpKNFprqCCXk5WjNGvUgB/CxxBR40QWSjsNK86+yvGKlCOGc7sbwfHLaXhkG+NsytwBaQ== + version "4.17.26" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz#5d9a8eeecb9d5f9d7fc1d85f541512a84638ae88" + integrity sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ== dependencies: "@types/node" "*" "@types/qs" "*" @@ -2066,14 +2077,14 @@ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/node@*": - version "16.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.7.tgz#36820945061326978c42a01e56b61cd223dfdc42" - integrity sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw== + version "16.11.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.12.tgz#ac7fb693ac587ee182c3780c26eb65546a1a3c10" + integrity sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw== "@types/node@^14.14.31", "@types/node@^14.14.9": - version "14.17.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.33.tgz#011ee28e38dc7aee1be032ceadf6332a0ab15b12" - integrity sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g== + version "14.18.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.0.tgz#98df2397f6936bfbff4f089e40e06fa5dd88d32a" + integrity sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ== "@types/parse-json@^4.0.0": version "4.0.0" @@ -2123,9 +2134,9 @@ "@types/react" "*" "@types/react@*": - version "17.0.35" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.35.tgz#217164cf830267d56cd1aec09dcf25a541eedd4c" - integrity sha512-r3C8/TJuri/SLZiiwwxQoLAoavaczARfT9up9b4Jr65+ErAUX3MIkU0oMOQnrpfgHme8zIqZLX7O5nnjm5Wayw== + version "17.0.37" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959" + integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2457,9 +2468,9 @@ acorn@^7.1.1: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.0.4, acorn@^8.2.4: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + version "8.6.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" + integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== adjust-sourcemap-loader@3.0.0: version "3.0.0" @@ -2543,9 +2554,9 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv-keywords@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.0.0.tgz#d01b3b21715b2f63d02aa511b82fc6eb3b30083c" - integrity sha512-ULd1QMjRoH6JDNUQIfDLrlE+OgZlFaxyYCjzt58uNuUQtKXt8/U+vK/8Ql0gyn/C5mqZzUWtKMqr/4YquvTrWA== + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== dependencies: fast-deep-equal "^3.1.3" @@ -2580,9 +2591,9 @@ ajv@8.6.2: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.8.0: - version "8.8.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.1.tgz#e73dd88eeb4b10bbcd82bee136e6fbe801664d18" - integrity sha512-6CiMNDrzv0ZR916u2T+iRunnD60uWmNn8SkdB44/6stVORUg0aAkWO7PkOhpCmjmW8f2I/G/xnowD66fxGyQJg== + version "8.8.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" + integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2613,6 +2624,13 @@ angulartics2@^10.0.0: dependencies: tslib "^2.0.0" +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-colors@4.1.1, ansi-colors@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" @@ -3177,6 +3195,20 @@ bootstrap@4.3.1: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac" integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag== +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3337,7 +3369,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.0, browserslist@^4.16.1, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.17.6, browserslist@^4.6.4, browserslist@^4.9.1: +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.0, browserslist@^4.16.1, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.6.4, browserslist@^4.9.1: version "4.18.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== @@ -3544,6 +3576,19 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -3610,9 +3655,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001032, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001165, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001280: - version "1.0.30001282" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd" - integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg== + version "1.0.30001285" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001285.tgz#fe1e52229187e11d6670590790d669b9e03315b7" + integrity sha512-KAOkuUtcQ901MtmvxfKD+ODHH9YVDYnBt+TGYSz2KIfnq22CiArbUxXPN9067gNbgMlnNYRSwho8OPXZPALB9Q== canonical-path@1.0.0: version "1.0.0" @@ -3723,10 +3768,15 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + ci-info@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" - integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -3778,6 +3828,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -3857,6 +3912,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -3939,9 +4001,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" - integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa" + integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -4085,6 +4147,18 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + connect-history-api-fallback@^1, connect-history-api-fallback@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" @@ -4262,11 +4336,11 @@ copy-webpack-plugin@^6.4.1: webpack-sources "^1.4.3" core-js-compat@^3.6.2, core-js-compat@^3.8.0: - version "3.19.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.19.1.tgz#fe598f1a9bf37310d77c3813968e9f7c7bb99476" - integrity sha512-Q/VJ7jAF/y68+aUsQJ/afPOewdsGkDtcMb40J8MbuWKlK3Y+wtHq8bTHKPj2WKWLIqmS5JhHs4CzHtz6pT2W6g== + version "3.19.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.19.3.tgz#de75e5821c5ce924a0a1e7b7d5c2cb973ff388aa" + integrity sha512-59tYzuWgEEVU9r+SRgceIGXSSUn47JknoiXW6Oq7RW8QHjXWz3/vp8pa7dbtuVu40sewz3OP3JmQEcDdztrLhA== dependencies: - browserslist "^4.17.6" + browserslist "^4.18.1" semver "7.0.0" core-js@3.6.4: @@ -4285,9 +4359,9 @@ core-js@^2.4.0: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.7.0: - version "3.19.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.1.tgz#f6f173cae23e73a7d88fa23b6e9da329276c6641" - integrity sha512-Tnc7E9iKd/b/ff7GFbhwPVzJzPztGrChB8X8GLqoYGdEOG8IpLnK1xPyo3ZoO3HsK6TodJS58VGPOxA+hLHQMg== + version "3.19.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.3.tgz#6df8142a996337503019ff3235a7022d7cdf4559" + integrity sha512-LeLBMgEGSsG7giquSzvgBrTS7V5UL6ks3eQlUSbN8dJStlLFiRzUm5iqsRyzUB8carhfKjkJ2vzKqE6z1Vga9g== core-util-is@1.0.2: version "1.0.2" @@ -4432,6 +4506,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + css-blank-pseudo@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" @@ -4688,9 +4767,9 @@ cssnano-preset-default@^4.0.7, cssnano-preset-default@^4.0.8: postcss-unique-selectors "^4.0.1" cssnano-preset-default@^5.0.1: - version "5.1.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.7.tgz#68c3ad1ec6a810482ec7d06b2d70fc34b6b0d70c" - integrity sha512-bWDjtTY+BOqrqBtsSQIbN0RLGD2Yr2CnecpP0ydHNafh9ZUEre8c8VYTaH9FEbyOt0eIfEUAYYk5zj92ioO8LA== + version "5.1.8" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.8.tgz#7525feb1b72f7b06e57f55064cbdae341d79dea2" + integrity sha512-zWMlP0+AMPBVE852SqTrP0DnhTcTA2C1wAF92TKZ3Va+aUVqLIhkqKlnJIXXdqXD7RN+S1ujuWmNpvrJBiM/vg== dependencies: css-declaration-sorter "^6.0.3" cssnano-utils "^2.0.1" @@ -4717,7 +4796,7 @@ cssnano-preset-default@^5.0.1: postcss-normalize-url "^5.0.3" postcss-normalize-whitespace "^5.0.1" postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.1" + postcss-reduce-initial "^5.0.2" postcss-reduce-transforms "^5.0.1" postcss-svgo "^5.0.3" postcss-unique-selectors "^5.0.2" @@ -4803,9 +4882,9 @@ cssstyle@^2.3.0: cssom "~0.3.6" csstype@^2.5.2: - version "2.6.18" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.18.tgz#980a8b53085f34af313410af064f2bd241784218" - integrity sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ== + version "2.6.19" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa" + integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ== csstype@^3.0.2: version "3.0.10" @@ -4933,10 +5012,10 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@4, debug@4.3.2, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -4947,7 +5026,14 @@ debug@4.3.1: dependencies: ms "2.1.2" -debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4983,6 +5069,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -4995,6 +5088,11 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-freeze@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" @@ -5018,7 +5116,7 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -default-gateway@^6.0.0: +default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== @@ -5032,6 +5130,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -5296,9 +5399,9 @@ domhandler@^3.0.0: domelementtype "^2.0.1" domhandler@^4.0.0, domhandler@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" - integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== dependencies: domelementtype "^2.2.0" @@ -5308,9 +5411,9 @@ domino@^2.1.2: integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ== dompurify@^2.0.11: - version "2.3.3" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c" - integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg== + version "2.3.4" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6" + integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ== domutils@^1.7.0: version "1.7.0" @@ -5349,6 +5452,11 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -5392,9 +5500,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.896: - version "1.3.901" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.901.tgz#ce2c3157d61bce9f42f1e83225c17358ae9f4918" - integrity sha512-ToJdV2vzwT2jeAsw8zIggTFllJ4Kxvwghk39AhJEHHlIxor10wsFI3wo69p8nFc0s/ATWBqugPv/k3nW4Y9Mww== + version "1.4.12" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.12.tgz#5f73d1278c6205fc41d7a0ebd75563046b77c5d8" + integrity sha512-zjfhG9Us/hIy8AlQ5OzfbR/C4aBv1Dg/ak4GX35CELYlJ4tDAtoEcQivXvyBdqdNQ+R6PhlgQqV8UNPJmhkJog== element-resize-detector@^1.2.1: version "1.2.3" @@ -5662,6 +5770,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -6175,9 +6288,9 @@ forever-agent@~0.6.1: integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= fork-ts-checker-webpack-plugin@^6.0.3: - version "6.4.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.4.0.tgz#057e477cf1d8b013b2ed2669437f818680289c4c" - integrity sha512-3I3wFkc4DbzaUDPWEi96wdYGu4EKtxBafhZYm0o4mX51d9bphAY4P3mBl8K5mFXFJqVzHfmdbm9kLGnm7vwwBg== + version "6.5.0" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" + integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== dependencies: "@babel/code-frame" "^7.8.3" "@types/json-schema" "^7.0.5" @@ -6380,7 +6493,7 @@ get-stdin@^8.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== @@ -6524,6 +6637,23 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" @@ -6643,6 +6773,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -6814,7 +6949,7 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -http-cache-semantics@^4.1.0: +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== @@ -6868,9 +7003,9 @@ http-errors@~1.7.2: toidentifier "1.0.0" http-parser-js@>=0.5.1: - version "0.5.3" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" - integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + version "0.5.5" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" + integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== http-proxy-agent@^4.0.1: version "4.0.1" @@ -6931,6 +7066,15 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== + dependencies: + assert-plus "^1.0.0" + jsprim "^2.0.2" + sshpk "^1.14.1" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -7029,6 +7173,11 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + ignore-walk@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" @@ -7105,6 +7254,11 @@ import-from@^3.0.0: dependencies: resolve-from "^5.0.0" +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -7174,7 +7328,7 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.4: +ini@^1.3.4, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -7206,16 +7360,6 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -internal-ip@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-6.2.0.tgz#d5541e79716e406b74ac6b07b856ef18dc1621c1" - integrity sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg== - dependencies: - default-gateway "^6.0.0" - ipaddr.js "^1.9.1" - is-ip "^3.1.0" - p-event "^4.2.0" - internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -7247,17 +7391,12 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip-regex@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" - integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== - ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= -ipaddr.js@1.9.1, ipaddr.js@^1.9.0, ipaddr.js@^1.9.1: +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== @@ -7348,6 +7487,13 @@ is-callable@^1.1.4, is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-ci@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" @@ -7476,7 +7622,7 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= -is-installed-globally@~0.4.0: +is-installed-globally@^0.4.0, is-installed-globally@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== @@ -7489,13 +7635,6 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== -is-ip@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8" - integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q== - dependencies: - ip-regex "^4.0.0" - is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -7506,6 +7645,11 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + is-number-like@^1.0.3: version "1.0.8" resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" @@ -7644,7 +7788,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typedarray@~1.0.0: +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= @@ -7683,6 +7827,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7769,9 +7918,9 @@ istanbul-lib-source-maps@^3.0.6: source-map "^0.6.1" istanbul-reports@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" - integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== + version "3.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.1.tgz#7085857f17d2441053c6ce5c3b8fdf6882289397" + integrity sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -7911,6 +8060,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -7931,10 +8085,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stringify-safe@~5.0.1: version "5.0.1" @@ -8011,13 +8165,23 @@ jsonschema@1.4.0: integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" - json-schema "0.2.3" + json-schema "0.4.0" + verror "1.10.0" + +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" verror "1.10.0" jss-plugin-camel-case@^10.5.1: @@ -8187,6 +8351,13 @@ karma@^5.2.3: ua-parser-js "0.7.22" yargs "^15.3.1" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -8239,6 +8410,13 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -8350,20 +8528,20 @@ limiter@^1.0.5: integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== listr2@^3.8.3: - version "3.13.4" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.4.tgz#34101fc0184545597e00d1e7915ccfbfb17332e6" - integrity sha512-lZ1Rut1DSIRwbxQbI8qaUBfOWJ1jEYRgltIM97j6kKOCI2pHVWMyxZvkU/JKmRBWcIYgDS2PK+yDgVqm7u3crw== + version "3.13.5" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.5.tgz#105a813f2eb2329c4aae27373a281d610ee4985f" + integrity sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA== dependencies: cli-truncate "^2.1.0" - clone "^2.1.2" colorette "^2.0.16" log-update "^4.0.0" p-map "^4.0.0" + rfdc "^1.3.0" rxjs "^7.4.0" through "^2.3.8" wrap-ansi "^7.0.0" @@ -8536,9 +8714,9 @@ log4js@^6.2.1: streamroller "^2.2.4" loglevel@^1.6.8: - version "1.7.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" - integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + version "1.8.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" + integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" @@ -8554,6 +8732,16 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -8672,9 +8860,9 @@ media-typer@0.3.0: integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= memfs@^3.1.2, memfs@^3.2.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.3.0.tgz#4da2d1fc40a04b170a56622c7164c6be2c4cbef2" - integrity sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg== + version "3.4.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.0.tgz#8bc12062b973be6b295d4340595736a656f0a257" + integrity sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA== dependencies: fs-monkey "1.0.3" @@ -8788,7 +8976,7 @@ mime@1.6.0, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.3.1, mime@^2.4.4, mime@^2.4.5: +mime@^2.4.4, mime@^2.4.5: version "2.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== @@ -8798,6 +8986,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + mini-css-extract-plugin@0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.10.0.tgz#a0e6bfcad22a9c73f6c882a3c7557a98e2d3d27d" @@ -9029,6 +9222,11 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +mrmime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b" + integrity sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -9242,6 +9440,22 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +nodemon@^2.0.15: + version "2.0.15" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e" + integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA== + dependencies: + chokidar "^3.5.2" + debug "^3.2.7" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.8" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + update-notifier "^5.1.0" + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -9249,6 +9463,13 @@ nopt@^5.0.0: dependencies: abbrev "1" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -9281,7 +9502,7 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -normalize-url@^4.5.0: +normalize-url@^4.1.0, normalize-url@^4.5.0: version "4.5.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== @@ -9449,9 +9670,9 @@ object-copy@^0.1.0: kind-of "^3.0.3" object-inspect@^1.11.0, object-inspect@^1.9.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" - integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== + version "1.11.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.1.tgz#d4bd7d7de54b9a75599f59a00bd698c1f1c6549b" + integrity sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA== object-is@^1.0.1: version "1.1.5" @@ -9674,12 +9895,10 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= -p-event@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" - integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== - dependencies: - p-timeout "^3.1.0" +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== p-finally@^1.0.0: version "1.0.0" @@ -9748,18 +9967,21 @@ p-retry@^4.5.0: "@types/retry" "^0.12.0" retry "^0.13.1" -p-timeout@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + pacote@11.2.4: version "11.2.4" resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.2.4.tgz#dc7ca740a573ed86a3bf863511d22c1d413ec82f" @@ -10857,12 +11079,12 @@ postcss-reduce-initial@^4.0.3: has "^1.0.0" postcss "^7.0.0" -postcss-reduce-initial@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz#9d6369865b0f6f6f6b165a0ef5dc1a4856c7e946" - integrity sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw== +postcss-reduce-initial@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz#fa424ce8aa88a89bc0b6d0f94871b24abe94c048" + integrity sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw== dependencies: - browserslist "^4.16.0" + browserslist "^4.16.6" caniuse-api "^3.0.0" postcss-reduce-transforms@^4.0.2: @@ -10991,9 +11213,9 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3: integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" @@ -11049,13 +11271,13 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" postcss@^8.1.4, postcss@^8.3.7: - version "8.3.11" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" - integrity sha512-hCmlUAIlUiav8Xdqw3Io4LcpA1DOt7h3LSTAC4G6JGHFFaWzI6qvFt9oilvl8BmkbBRX1IhM90ZAmpk68zccQA== + version "8.4.4" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.4.tgz#d53d4ec6a75fd62557a66bb41978bf47ff0c2869" + integrity sha512-joU6fBsN6EIer28Lj6GDFoC/5yOZzLCfn0zHAn/MYXI7aPt4m4hK5KC5ovEZXy+lnCjmYIbQWngvju2ddyEr8Q== dependencies: nanoid "^3.1.30" picocolors "^1.0.0" - source-map-js "^0.6.2" + source-map-js "^1.0.1" prelude-ls@~1.1.2: version "1.1.2" @@ -11067,6 +11289,11 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + pretty-bytes@^5.3.0, pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -11187,6 +11414,11 @@ psl@^1.1.28, psl@^1.1.33: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -11239,6 +11471,13 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + q@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" @@ -11368,6 +11607,16 @@ raw-loader@4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" +rc@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + re-reselect@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-4.0.0.tgz#9ddec4c72c4d952f68caa5aa4b76a9ed38b75cac" @@ -11476,9 +11725,9 @@ react-full-screen@^0.2.4: fscreen "^1.0.1" react-i18next@^11.7.0: - version "11.14.2" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.14.2.tgz#2ff28f6a0ddf06eaf79435ed6c70c441d709cf34" - integrity sha512-fmDhwNA0zDmSEL3BBT5qwNMvxrKu25oXDDAZyHprfB0AHZmWXfBmRLf8MX8i1iBd2I2C2vsA2D9wxYBIwzooEQ== + version "11.14.3" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.14.3.tgz#b44b5c4d1aadac5211be011827a2830be60f2522" + integrity sha512-Hf2aanbKgYxPjG8ZdKr+PBz9sY6sxXuZWizxCYyJD2YzvJ0W9JTQcddVEjDaKyBoCyd3+5HTerdhc9ehFugc6g== dependencies: "@babel/runtime" "^7.14.5" html-parse-stringify "^3.0.1" @@ -11662,9 +11911,9 @@ redux-saga@^1.1.3: "@redux-saga/core" "^1.1.3" redux-thunk@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.0.tgz#ac89e1d6b9bdb9ee49ce69a69071be41bbd82d67" - integrity sha512-/y6ZKQNU/0u8Bm7ROLq9Pt/7lU93cT0IucYMrubo89ENjxPa7i8pqLKu6V4X7/TvYovQ6x01unTeyeZ9lgXiTA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== redux@^4.0.0, redux@^4.0.4, redux@^4.0.5: version "4.1.2" @@ -11745,6 +11994,20 @@ regexpu-core@^4.7.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + regjsgen@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" @@ -11842,9 +12105,9 @@ requires-port@^1.0.0: integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= reselect@^4.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.4.tgz#66df0aff41b6ee0f51e2cc17cfaf2c1995916f32" - integrity sha512-i1LgXw8DKSU5qz1EV0ZIKz4yIUHJ7L3bODh+Da6HmVSm9vdL/hG7IpbgzQ3k2XSirzf8/eI7OMEs81gb1VV2fQ== + version "4.1.5" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6" + integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ== resolve-cwd@^2.0.0: version "2.0.0" @@ -11931,6 +12194,13 @@ resp-modifier@6.0.2: debug "^2.2.0" minimatch "^3.0.2" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -11977,7 +12247,7 @@ rework@1.0.1: convert-source-map "^0.3.3" css "^2.0.0" -rfdc@^1.1.4: +rfdc@^1.1.4, rfdc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== @@ -12029,9 +12299,9 @@ rollup@2.38.4: fsevents "~2.3.1" rtl-css-js@^1.13.1: - version "1.14.5" - resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.14.5.tgz#fd92685acd024e688dda899a28c5fb9270b79974" - integrity sha512-+ng7LWVvPjQUdgDVviR6vKi2X4JiBtlw5rdY0UM5/Cj39c2/KDUsY/VxEzGE25m4KR5g0dvuKfrDq7DaoDooIA== + version "1.15.0" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.15.0.tgz#680ed816e570a9ebccba9e1cd0f202c6a8bb2dc0" + integrity sha512-99Cu4wNNIhrI10xxUaABHsdDqzalrSRTie4GeCmbGVuehm4oj+fIy8fTzB+16pmKe8Bv9rl+hxIBez6KxExTew== dependencies: "@babel/runtime" "^7.1.2" @@ -12241,7 +12511,7 @@ schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6 ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@^3.0.0, schema-utils@^3.1.0: +schema-utils@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== @@ -12289,6 +12559,13 @@ selfsigned@^1.10.11, selfsigned@^1.10.7, selfsigned@^1.10.8: dependencies: node-forge "^0.10.0" +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + semver-dsl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0" @@ -12320,12 +12597,12 @@ semver@7.3.4: dependencies: lru-cache "^6.0.0" -semver@^5.0.0, semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +semver@^5.0.0, semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -12526,9 +12803,9 @@ side-channel@^1.0.4: object-inspect "^1.9.0" signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.5" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" - integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== + version "3.0.6" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" + integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== simple-swizzle@^0.2.2: version "0.2.2" @@ -12538,12 +12815,12 @@ simple-swizzle@^0.2.2: is-arrayish "^0.3.1" sirv@^1.0.7: - version "1.0.18" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.18.tgz#105fab52fb656ce8a2bebbf36b11052005952899" - integrity sha512-f2AOPogZmXgJ9Ma2M22ZEhc1dNtRIzcEkiflMFeVTRq+OViOZMvH1IPMVOwrKaxpSaHioBJiDR0SluRqGa7atA== + version "1.0.19" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" + integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== dependencies: "@polka/url" "^1.0.0-next.20" - mime "^2.3.1" + mrmime "^1.0.0" totalist "^1.0.0" sisteransi@^1.0.5: @@ -12707,12 +12984,12 @@ sockjs@0.3.20: websocket-driver "0.6.5" sockjs@^0.3.21: - version "0.3.21" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" - integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== dependencies: faye-websocket "^0.11.3" - uuid "^3.4.0" + uuid "^8.3.2" websocket-driver "^0.7.4" socks-proxy-agent@^5.0.0: @@ -12749,10 +13026,10 @@ source-list-map@^2.0.0, source-list-map@^2.0.1: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== +source-map-js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" + integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== source-map-loader@1.0.2: version "1.0.2" @@ -12804,10 +13081,10 @@ source-map-support@0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@^0.5.17, source-map-support@^0.5.5, source-map-support@~0.5.12, source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== +source-map-support@^0.5.17, source-map-support@^0.5.5, source-map-support@~0.5.12, source-map-support@~0.5.19, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -12819,14 +13096,6 @@ source-map-support@~0.4.0: dependencies: source-map "^0.5.6" -source-map-support@~0.5.19: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-url@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" @@ -12911,7 +13180,7 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: +sshpk@^1.14.1, sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== @@ -13060,7 +13329,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13153,6 +13422,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + style-loader@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" @@ -13225,7 +13499,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -13529,6 +13803,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -13574,6 +13853,13 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -13722,6 +14008,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -13745,6 +14036,13 @@ type@^2.5.0: resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -13809,6 +14107,11 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + unfetch@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" @@ -13871,6 +14174,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + universal-analytics@0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/universal-analytics/-/universal-analytics-0.4.23.tgz#d915e676850c25c4156762471bdd7cf2eaaca8ac" @@ -13918,6 +14228,26 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -13930,6 +14260,13 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + url-parse@^1.4.3, url-parse@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" @@ -14295,9 +14632,9 @@ webpack-dev-server@3.11.2: yargs "^13.3.2" webpack-dev-server@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.5.0.tgz#614b5112cfa4730a4801bb4ddebb3be5b0d70497" - integrity sha512-Ss4WptsUjYa+3hPI4iYZYEc8FrtnfkaPrm5WTjk9ux5kiCS718836srs0ppKMHRaCHP5mQ6g4JZGcfDdGbCjpQ== + version "4.6.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.6.0.tgz#e8648601c440172d9b6f248d28db98bed335315a" + integrity sha512-oojcBIKvx3Ya7qs1/AVWHDgmP1Xml8rGsEBnSobxU/UJSX1xP1GPM3MwsAnDzvqcVmVki8tV7lbcsjEjk0PtYg== dependencies: ansi-html-community "^0.0.8" bonjour "^3.5.0" @@ -14305,17 +14642,17 @@ webpack-dev-server@^4.5.0: colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^1.6.0" + default-gateway "^6.0.3" del "^6.0.0" express "^4.17.1" graceful-fs "^4.2.6" html-entities "^2.3.2" http-proxy-middleware "^2.0.0" - internal-ip "^6.2.0" ipaddr.js "^2.0.1" open "^8.0.9" p-retry "^4.5.0" portfinder "^1.0.28" - schema-utils "^3.1.0" + schema-utils "^4.0.0" selfsigned "^1.10.11" serve-index "^1.9.1" sockjs "^0.3.21" @@ -14565,6 +14902,13 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2 || 3 || 4" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -14621,6 +14965,16 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + ws@^6.2.1: version "6.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" @@ -14629,20 +14983,25 @@ ws@^6.2.1: async-limiter "~1.0.0" ws@^7.3.1, ws@^7.4.6: - version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" - integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + version "7.5.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== ws@^8.1.0: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + version "8.3.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d" + integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw== ws@~7.4.2: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xhr2@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.2.1.tgz#4e73adc4f9cfec9cbd2157f73efdce3a5f108a93" From 46bb3e109c9bd2abcc965fdf90f0a210cf0dcb25 Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 7 Dec 2021 11:38:48 -0600 Subject: [PATCH 23/59] cleanup and readme updates --- README.md | 155 +++++++++++++++++++++++++----------- docs/Configuration.md | 74 ++++++++++++----- src/config/config.server.ts | 4 +- src/config/config.util.ts | 6 +- 4 files changed, 165 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 69b6132478..41df8d0223 100644 --- a/README.md +++ b/README.md @@ -102,18 +102,18 @@ Installing ### Configuring -Default configuration file is located in `src/environments/` folder. +Default configuration file is located in `config/` folder. -To change the default configuration values, create local files that override the parameters you need to change. You can use `environment.template.ts` as a starting point. +To change the default configuration values, create local files that override the parameters you need to change. You can use `appConfig.json` as a starting point. -- Create a new `environment.dev.ts` file in `src/environments/` for a `development` environment; -- Create a new `environment.prod.ts` file in `src/environments/` for a `production` environment; +- Create a new `appConfig.(dev or development).json` file in `config/` for a `development` environment; +- Create a new `appConfig.(prod or production).ts` file in `config/` for a `production` environment; -The server settings can also be overwritten using an environment file. +The settings can also be overwritten using an environment file or environment variables. This file should be called `.env` and be placed in the project root. -The following settings can be overwritten in this file: +The following non-convention settings: ```bash DSPACE_HOST # The host name of the angular application @@ -127,23 +127,44 @@ DSPACE_REST_NAMESPACE # The namespace of the REST application DSPACE_REST_SSL # Whether the angular REST uses SSL [true/false] ``` +All other settings can be set using the following convention for naming the environment variables: + +1. replace all `.` with `_` +2. convert all characters to upper case + +e.g. + +``` +cache.msToLive.default => CACHE_MSTOLIVE_DEFAULT +auth.ui.timeUntilIdle => AUTH_UI_TIMEUNTILIDLE +``` + The same settings can also be overwritten by setting system environment variables instead, E.g.: ```bash export DSPACE_HOST=api7.dspace.org ``` -The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides **`environment.(prod, dev or test).ts`** overrides **`environment.common.ts`** +The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `APP_CONFIG_PATH` overrides **`appConfig.(prod or dev).json`** -These configuration sources are collected **at build time**, and written to `src/environments/environment.ts`. At runtime the configuration is fixed, and neither `.env` nor the process' environment will be consulted. +These configuration sources are collected **at run time**, and written to `dist/browser/assets/appConfig.json` for production and `src/app/assets/appConfig.json` for development. + +The configuration file can be externalized by using environment variable `APP_CONFIG_PATH`. #### Using environment variables in code To use environment variables in a UI component, use: ```typescript -import { environment } from '../environment.ts'; +import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; +... +constructor(@Inject(APP_CONFIG) private appConfig: AppConfig) {} +... ``` -This file is generated by the script located in `scripts/set-env.ts`. This script will run automatically before every build, or can be manually triggered using the appropriate `config` script in `package.json` +or + +```typescript +import { environment } from '../environment.ts'; +``` Running the app @@ -230,7 +251,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con The test files can be found in the `./cypress/integration/` folder. -Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `environment.prod.ts` or `environment.common.ts`. You may override this using env variables, see [Configuring](#configuring). +Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `appConfig.prod.json` or `appConfig.json`. You may override this using env variables, see [Configuring](#configuring). Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results. @@ -309,49 +330,87 @@ File Structure ``` dspace-angular -├── README.md * This document -├── app.yaml * Application manifest file -├── config * Folder for configuration files -│   ├── environment.default.js * Default configuration files -│   └── environment.test.js * Test configuration files -├── docs * Folder for documentation +├── config * +│ └── appConfig.json * Default app config ├── cypress * Folder for Cypress (https://cypress.io/) / e2e tests -│   ├── integration * Folder for e2e/integration test files -│   ├── fixtures * Folder for any fixtures needed by e2e tests -│   ├── plugins * Folder for Cypress plugins (if any) -│   ├── support * Folder for global e2e test actions/commands (run for all tests) -│   └── tsconfig.json * TypeScript configuration file for e2e tests +│ ├── downloads * +│ ├── fixtures * Folder for e2e/integration test files +│ ├── integration * Folder for any fixtures needed by e2e tests +│ ├── plugins * Folder for Cypress plugins (if any) +│ ├── support * Folder for global e2e test actions/commands (run for all tests) +│ └── tsconfig.json * TypeScript configuration file for e2e tests +├── docker * +│ ├── cli.assetstore.yml * +│ ├── cli.ingest.yml * +│ ├── cli.yml * +│ ├── db.entities.yml * +│ ├── docker-compose-ci.yml * +│ ├── docker-compose-rest.yml * +│ ├── docker-compose.yml * +│ ├── environment.dev.ts * +│ ├── local.cfg * +│ └── README.md * +├── docs * Folder for documentation +│ └── Configuration.md * Configuration documentation +├── scripts * +│ ├── merge-i18n-files.ts * +│ ├── serve.ts * +│ ├── sync-i18n-files.ts * +│ ├── test-rest.ts * +│ └── webpack.js * +├── src * The source of the application +│ ├── app * The source code of the application, subdivided by module/page. +│ ├── assets * Folder for static resources +│ │ ├── fonts * Folder for fonts +│ │ ├── i18n * Folder for i18n translations +│ │ └── images * Folder for images +│ ├── backend * Folder containing a mock of the REST API, hosted by the express server +│ ├── config * +│ ├── environments * +│ │ ├── environment.prod.ts * Production configuration files +│ │ ├── environment.test.ts * Test configuration files +│ │ └── environment.ts * Default (development) configuration files +│ ├── mirador-viewer * +│ ├── modules * +│ ├── ngx-translate-loaders * +│ ├── styles * Folder containing global styles +│ ├── themes * Folder containing available themes +│ │ ├── custom * Template folder for creating a custom theme +│ │ └── dspace * Default 'dspace' theme +│ ├── index.csr.html * The index file for client side rendering fallback +│ ├── index.html * The index file +│ ├── main.browser.ts * The bootstrap file for the client +│ ├── main.server.ts * The express (http://expressjs.com/) config and bootstrap file for the server +│ ├── polyfills.ts * +│ ├── robots.txt * The robots.txt file +│ ├── test.ts * +│ └── typings.d.ts * +├── webpack * +│ ├── helpers.ts * Webpack helpers +│ ├── webpack.browser.ts * Webpack (https://webpack.github.io/) config for browser build +│ ├── webpack.common.ts * Webpack (https://webpack.github.io/) common build config +│ ├── webpack.mirador.config.ts * Webpack (https://webpack.github.io/) config for mirador config build +│ ├── webpack.prod.ts * Webpack (https://webpack.github.io/) config for prod build +│ └── webpack.test.ts * Webpack (https://webpack.github.io/) config for test build +├── angular.json * Angular CLI (https://angular.io/cli) configuration +├── cypress.json * Cypress Test (https://www.cypress.io/) configuration +├── Dockerfile * ├── karma.conf.js * Karma configuration file for Unit Test +├── LICENSE * +├── LICENSES_THIRD_PARTY * ├── nodemon.json * Nodemon (https://nodemon.io/) configuration ├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc. -├── postcss.config.js * PostCSS (http://postcss.org/) configuration file -├── src * The source of the application -│   ├── app * The source code of the application, subdivided by module/page. -│   ├── assets * Folder for static resources -│   │   ├── fonts * Folder for fonts -│   │   ├── i18n * Folder for i18n translations -│   | └── en.json5 * i18n translations for English -│   │   └── images * Folder for images -│   ├── backend * Folder containing a mock of the REST API, hosted by the express server -│   ├── config * -│   ├── index.csr.html * The index file for client side rendering fallback -│   ├── index.html * The index file -│   ├── main.browser.ts * The bootstrap file for the client -│   ├── main.server.ts * The express (http://expressjs.com/) config and bootstrap file for the server -│   ├── robots.txt * The robots.txt file -│   ├── modules * -│   ├── styles * Folder containing global styles -│   └── themes * Folder containing available themes -│      ├── custom * Template folder for creating a custom theme -│      └── dspace * Default 'dspace' theme -├── tsconfig.json * TypeScript config +├── postcss.config.js * PostCSS (http://postcss.org/) configuration +├── README.md * This document +├── SECURITY.md * +├── server.ts * Angular Universal Node.js Express server +├── tsconfig.app.json * TypeScript config for browser (app) +├── tsconfig.json * TypeScript common config +├── tsconfig.server.json * TypeScript config for server +├── tsconfig.spec.json * TypeScript config for tests +├── tsconfig.ts-node.json * TypeScript config for using ts-node directly ├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration ├── typedoc.json * TYPEDOC configuration -├── webpack * Webpack (https://webpack.github.io/) config directory -│   ├── webpack.browser.ts * Webpack (https://webpack.github.io/) config for client build -│   ├── webpack.common.ts * -│   ├── webpack.prod.ts * Webpack (https://webpack.github.io/) config for production build -│   └── webpack.test.ts * Webpack (https://webpack.github.io/) config for test build └── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock) ``` diff --git a/docs/Configuration.md b/docs/Configuration.md index f4fff1166c..82aaa42c40 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,26 +1,32 @@ # Configuration -Default configuration file is located in `src/environments/` folder. All configuration options should be listed in the default configuration file `src/environments/environment.common.ts`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change. You can use `environment.template.ts` as a starting point. +Default configuration file is located in `config/` folder. All configuration options should be listed in the default configuration file `src/config/default-app-config.ts`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change. You can use `appConfig.json` as a starting point. -- Create a new `environment.dev.ts` file in `src/environments/` for `development` environment; -- Create a new `environment.prod.ts` file in `src/environments/` for `production` environment; +- Create a new `appConfig.(dev or development).json` file in `config/` for `development` environment; +- Create a new `appConfig.(prod or production).json` file in `config/` for `production` environment; -Some few configuration options can be overridden by setting environment variables. These and the variable names are listed below. +Alternatively, create a desired app config file at an external location and set the path as environment variable `APP_CONFIG_PATH`. + +e.g. +``` +APP_CONFIG_PATH=/usr/local/dspace/config/appConfig.json +``` + +Configuration options can be overridden by setting environment variables. ## Nodejs server When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`. To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above): ``` -export const environment = { - // Angular UI settings. - ui: { - ssl: false, - host: 'localhost', - port: 4000, - nameSpace: '/' +{ + "ui": { + "ssl": false, + "host": "localhost", + "port": 4000, + "nameSpace": "/" } -}; +} ``` Alternately you can set the following environment variables. If any of these are set, it will override all configuration files: @@ -30,21 +36,26 @@ Alternately you can set the following environment variables. If any of these are DSPACE_PORT=4000 DSPACE_NAMESPACE=/ ``` +or +``` + UI_SSL=true + UI_HOST=localhost + UI_PORT=4000 + UI_NAMESPACE=/ +``` ## DSpace's REST endpoint dspace-angular connects to your DSpace installation by using its REST endpoint. To do so, you have to define the ip address, port and if ssl should be enabled. You can do this in a configuration file (see above) by adding the following options: ``` -export const environment = { - // The REST API server settings. - rest: { - ssl: true, - host: 'api7.dspace.org', - port: 443, - // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript - nameSpace: '/server' +{ + "rest": { + "ssl": true, + "host": "api7.dspace.org", + "port": 443, + "nameSpace": "/server" } -}; +} ``` Alternately you can set the following environment variables. If any of these are set, it will override all configuration files: @@ -54,6 +65,27 @@ Alternately you can set the following environment variables. If any of these are DSPACE_REST_PORT=443 DSPACE_REST_NAMESPACE=/server ``` +or +``` + REST_SSL=true + REST_HOST=api7.dspace.org + REST_PORT=443 + REST_NAMESPACE=/server +``` + +## Environment variable naming convention + +Settings can be set using the following convention for naming the environment variables: + +1. replace all `.` with `_` +2. convert all characters to upper case + +e.g. + +``` +cache.msToLive.default => CACHE_MSTOLIVE_DEFAULT +auth.ui.timeUntilIdle => AUTH_UI_TIMEUNTILIDLE +``` ## Supporting analytics services other than Google Analytics This project makes use of [Angulartics](https://angulartics.github.io/angulartics2/) to track usage events and send them to Google Analytics. diff --git a/src/config/config.server.ts b/src/config/config.server.ts index 51a8c8119f..da33f553a3 100644 --- a/src/config/config.server.ts +++ b/src/config/config.server.ts @@ -6,7 +6,7 @@ import { AppConfig } from './app-config.interface'; import { Config } from './config.interface'; import { DefaultAppConfig } from './default-app-config'; import { ServerConfig } from './server-config.interface'; -import { extendConfig } from './config.util'; +import { mergeConfig } from './config.util'; import { isNotEmpty } from '../app/shared/empty.util'; const CONFIG_PATH = join(process.cwd(), 'config'); @@ -78,7 +78,7 @@ const overrideWithConfig = (config: Config, pathToConfig: string) => { try { console.log(`Overriding app config with ${pathToConfig}`); const externalConfig = fs.readFileSync(pathToConfig, 'utf8'); - extendConfig(config, JSON.parse(externalConfig)); + mergeConfig(config, JSON.parse(externalConfig)); } catch (err) { console.error(err); } diff --git a/src/config/config.util.ts b/src/config/config.util.ts index b576e99eba..9f2d7d349e 100644 --- a/src/config/config.util.ts +++ b/src/config/config.util.ts @@ -8,11 +8,11 @@ import { AppConfig } from './app-config.interface'; import { ThemeConfig } from './theme.model'; const extendEnvironmentWithAppConfig = (env: any, appConfig: AppConfig): void => { - extendConfig(env, appConfig); + mergeConfig(env, appConfig); console.log(`Environment extended with app config`); }; -const extendConfig = (config: any, appConfig: AppConfig): void => { +const mergeConfig = (config: any, appConfig: AppConfig): void => { const mergeOptions = { arrayMerge: (destinationArray, sourceArray, options) => sourceArray }; @@ -32,6 +32,6 @@ const getDefaultThemeConfig = (): ThemeConfig => { export { extendEnvironmentWithAppConfig, - extendConfig, + mergeConfig, getDefaultThemeConfig }; From 18dd2ad8841476d0e960ae5e08b641508d4325be Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 7 Dec 2021 14:45:46 -0600 Subject: [PATCH 24/59] add simple config util test --- src/config/config.util.spec.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/config/config.util.spec.ts diff --git a/src/config/config.util.spec.ts b/src/config/config.util.spec.ts new file mode 100644 index 0000000000..cb2fdcdba2 --- /dev/null +++ b/src/config/config.util.spec.ts @@ -0,0 +1,17 @@ +import { environment } from '../environments/environment.prod'; +import { extendEnvironmentWithAppConfig } from './config.util'; +import { DefaultAppConfig } from './default-app-config'; + +describe('Config Util', () => { + describe('extendEnvironmentWithAppConfig', () => { + it('should extend prod environment with app config', () => { + const appConfig = new DefaultAppConfig(); + const originalMsToLive = appConfig.cache.msToLive.default; + expect(originalMsToLive).toEqual(15 * 60 * 1000); // 15 minute + const msToLive = 1 * 60 * 1000; // 1 minute + appConfig.cache.msToLive.default = msToLive; + extendEnvironmentWithAppConfig(environment, appConfig); + expect(environment.cache.msToLive.default).toEqual(msToLive); + }); + }); +}); From bc999d0b5f13e603fabc7668fbc2c867a4a29526 Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 7 Dec 2021 21:33:43 -0600 Subject: [PATCH 25/59] minor refactoring and more config util assertions --- src/config/config.server.ts | 29 ++++++++++++++--------- src/config/config.util.spec.ts | 43 ++++++++++++++++++++++++++++++++-- src/config/config.util.ts | 25 ++++++++++++++++---- 3 files changed, 80 insertions(+), 17 deletions(-) diff --git a/src/config/config.server.ts b/src/config/config.server.ts index da33f553a3..5e8d8e1587 100644 --- a/src/config/config.server.ts +++ b/src/config/config.server.ts @@ -11,8 +11,6 @@ import { isNotEmpty } from '../app/shared/empty.util'; const CONFIG_PATH = join(process.cwd(), 'config'); -const APP_CONFIG_PATH = join(CONFIG_PATH, 'appConfig.json'); - type Environment = 'production' | 'development' | 'test'; const getBooleanFromString = (variable: string): boolean => { @@ -47,7 +45,7 @@ const getEnvironment = (): Environment => { const getLocalConfigPath = (env: Environment) => { // default to config/appConfig.json - let localConfigPath = APP_CONFIG_PATH; + let localConfigPath = join(CONFIG_PATH, 'appConfig.json'); // determine app config filename variations let envVariations; @@ -65,9 +63,9 @@ const getLocalConfigPath = (env: Environment) => { // check if any environment variations of app config exist for (const envVariation of envVariations) { - const altDistConfigPath = join(CONFIG_PATH, `appConfig.${envVariation}.json`); - if (fs.existsSync(altDistConfigPath)) { - localConfigPath = altDistConfigPath; + const envLocalConfigPath = join(CONFIG_PATH, `appConfig.${envVariation}.json`); + if (fs.existsSync(envLocalConfigPath)) { + localConfigPath = envLocalConfigPath; } } @@ -106,7 +104,6 @@ const overrideWithEnvironment = (config: Config, key: string = '') => { default: console.warn(`Unsupported environment variable type ${typeof innerConfig} ${variable}`); } - } } } @@ -122,6 +119,16 @@ const buildBaseUrl = (config: ServerConfig): void => { ].join(''); }; +/** + * Build app config with the following chain of override. + * + * local config -> environment local config -> external config -> environment variable + * + * Optionally save to file. + * + * @param destConfigPath optional path to save config file + * @returns app config + */ export const buildAppConfig = (destConfigPath?: string): AppConfig => { // start with default app config const appConfig: AppConfig = new DefaultAppConfig(); @@ -141,11 +148,11 @@ export const buildAppConfig = (destConfigPath?: string): AppConfig => { } // override with dist config - const distConfigPath = getLocalConfigPath(env); - if (fs.existsSync(distConfigPath)) { - overrideWithConfig(appConfig, distConfigPath); + const localConfigPath = getLocalConfigPath(env); + if (fs.existsSync(localConfigPath)) { + overrideWithConfig(appConfig, localConfigPath); } else { - console.warn(`Unable to find dist config file at ${distConfigPath}`); + console.warn(`Unable to find dist config file at ${localConfigPath}`); } // override with external config if specified by environment variable `APP_CONFIG_PATH` diff --git a/src/config/config.util.spec.ts b/src/config/config.util.spec.ts index cb2fdcdba2..f9989f5a83 100644 --- a/src/config/config.util.spec.ts +++ b/src/config/config.util.spec.ts @@ -1,17 +1,56 @@ import { environment } from '../environments/environment.prod'; import { extendEnvironmentWithAppConfig } from './config.util'; import { DefaultAppConfig } from './default-app-config'; +import { HandleThemeConfig } from './theme.model'; describe('Config Util', () => { describe('extendEnvironmentWithAppConfig', () => { it('should extend prod environment with app config', () => { const appConfig = new DefaultAppConfig(); - const originalMsToLive = appConfig.cache.msToLive.default; - expect(originalMsToLive).toEqual(15 * 60 * 1000); // 15 minute + expect(appConfig.cache.msToLive.default).toEqual(15 * 60 * 1000); // 15 minute + expect(appConfig.ui.rateLimiter.windowMs).toEqual(1 * 60 * 1000); // 1 minute + expect(appConfig.ui.rateLimiter.max).toEqual(500); + + expect(appConfig.submission.autosave.metadata).toEqual([]); + + expect(appConfig.themes.length).toEqual(1); + expect(appConfig.themes[0].name).toEqual('dspace'); + const msToLive = 1 * 60 * 1000; // 1 minute appConfig.cache.msToLive.default = msToLive; + + const rateLimiter = { + windowMs: 5 * 50 * 1000, // 5 minutes + max: 1000 + }; + appConfig.ui.rateLimiter = rateLimiter; + + const autoSaveMetadata = [ + 'dc.author', + 'dc.title' + ]; + + appConfig.submission.autosave.metadata = autoSaveMetadata; + + const customTheme: HandleThemeConfig = { + name: 'custom', + handle: '10673/1233' + }; + + appConfig.themes.push(customTheme); + extendEnvironmentWithAppConfig(environment, appConfig); + expect(environment.cache.msToLive.default).toEqual(msToLive); + expect(environment.ui.rateLimiter.windowMs).toEqual(rateLimiter.windowMs); + expect(environment.ui.rateLimiter.max).toEqual(rateLimiter.max); + expect(environment.submission.autosave.metadata[0]).toEqual(autoSaveMetadata[0]); + expect(environment.submission.autosave.metadata[1]).toEqual(autoSaveMetadata[1]); + + expect(environment.themes.length).toEqual(2); + expect(environment.themes[0].name).toEqual('dspace'); + expect(environment.themes[1].name).toEqual(customTheme.name); + expect((environment.themes[1] as HandleThemeConfig).handle).toEqual(customTheme.handle); }); }); }); diff --git a/src/config/config.util.ts b/src/config/config.util.ts index 9f2d7d349e..6c3b9e0f7a 100644 --- a/src/config/config.util.ts +++ b/src/config/config.util.ts @@ -7,21 +7,38 @@ import { hasNoValue } from '../app/shared/empty.util'; import { AppConfig } from './app-config.interface'; import { ThemeConfig } from './theme.model'; +/** + * Extend Angular environment with app config. + * + * @param env environment object + * @param appConfig app config + */ const extendEnvironmentWithAppConfig = (env: any, appConfig: AppConfig): void => { mergeConfig(env, appConfig); console.log(`Environment extended with app config`); }; -const mergeConfig = (config: any, appConfig: AppConfig): void => { +/** + * Merge one config into another. + * + * @param destinationConfig destination config + * @param sourceConfig source config + */ +const mergeConfig = (destinationConfig: any, sourceConfig: AppConfig): void => { const mergeOptions = { arrayMerge: (destinationArray, sourceArray, options) => sourceArray }; - Object.assign(config, merge.all([ - config, - appConfig + Object.assign(destinationConfig, merge.all([ + destinationConfig, + sourceConfig ], mergeOptions)); }; +/** + * Get default them config from environment. + * + * @returns default theme config + */ const getDefaultThemeConfig = (): ThemeConfig => { return environment.themes.find((themeConfig: any) => hasNoValue(themeConfig.regex) && From 10622008c407fdb77d3df24507cc580e75862685 Mon Sep 17 00:00:00 2001 From: William Welling Date: Wed, 8 Dec 2021 09:38:52 -0600 Subject: [PATCH 26/59] refactor config filename and convert to yaml --- README.md | 14 +- config/.gitignore | 3 +- config/appConfig.json | 8 -- config/config.example.yml | 231 +++++++++++++++++++++++++++++++ config/config.yml | 5 + docs/Configuration.md | 8 +- package.json | 6 +- scripts/env-to-yaml.ts | 32 +++++ server.ts | 2 +- src/app/app.component.ts | 2 - src/assets/.gitignore | 2 +- src/config/config.server.ts | 22 ++- src/config/default-app-config.ts | 24 ++-- src/main.browser.ts | 2 +- webpack/webpack.browser.ts | 2 +- yarn.lock | 12 ++ 16 files changed, 330 insertions(+), 45 deletions(-) delete mode 100644 config/appConfig.json create mode 100644 config/config.example.yml create mode 100644 config/config.yml create mode 100644 scripts/env-to-yaml.ts diff --git a/README.md b/README.md index 41df8d0223..06c977915a 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,10 @@ Installing Default configuration file is located in `config/` folder. -To change the default configuration values, create local files that override the parameters you need to change. You can use `appConfig.json` as a starting point. +To override the default configuration values, create local files that override the parameters you need to change. You can use `config.example.yml` as a starting point. -- Create a new `appConfig.(dev or development).json` file in `config/` for a `development` environment; -- Create a new `appConfig.(prod or production).ts` file in `config/` for a `production` environment; +- Create a new `config.(dev or development).yml` file in `config/` for a `development` environment; +- Create a new `config.(prod or production).yml` file in `config/` for a `production` environment; The settings can also be overwritten using an environment file or environment variables. @@ -144,9 +144,9 @@ The same settings can also be overwritten by setting system environment variable export DSPACE_HOST=api7.dspace.org ``` -The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `APP_CONFIG_PATH` overrides **`appConfig.(prod or dev).json`** +The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`** -These configuration sources are collected **at run time**, and written to `dist/browser/assets/appConfig.json` for production and `src/app/assets/appConfig.json` for development. +These configuration sources are collected **at run time**, and written to `dist/browser/assets/config.json` for production and `src/app/assets/config.json` for development. The configuration file can be externalized by using environment variable `APP_CONFIG_PATH`. @@ -251,7 +251,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con The test files can be found in the `./cypress/integration/` folder. -Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `appConfig.prod.json` or `appConfig.json`. You may override this using env variables, see [Configuring](#configuring). +Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `config.prod.yml` or `config.yml`. You may override this using env variables, see [Configuring](#configuring). Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results. @@ -331,7 +331,7 @@ File Structure ``` dspace-angular ├── config * -│ └── appConfig.json * Default app config +│ └── config.yml * Default app config ├── cypress * Folder for Cypress (https://cypress.io/) / e2e tests │ ├── downloads * │ ├── fixtures * Folder for e2e/integration test files diff --git a/config/.gitignore b/config/.gitignore index e1899191e1..a420ca4302 100644 --- a/config/.gitignore +++ b/config/.gitignore @@ -1 +1,2 @@ -appConfig.*.json +config.*.yml +!config.example.yml diff --git a/config/appConfig.json b/config/appConfig.json deleted file mode 100644 index a70d8ea6ab..0000000000 --- a/config/appConfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rest": { - "ssl": true, - "host": "api7.dspace.org", - "port": 443, - "nameSpace": "/server" - } -} diff --git a/config/config.example.yml b/config/config.example.yml new file mode 100644 index 0000000000..165d94b7bf --- /dev/null +++ b/config/config.example.yml @@ -0,0 +1,231 @@ +# NOTE: will log all redux actions and transfers in console +debug: false + +# Angular Universal server settings +# NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. +ui: + ssl: false + host: localhost + port: 4000 + # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: / + # The rateLimiter settings limit each IP to a 'max' of 500 requests per 'windowMs' (1 minute). + rateLimiter: + windowMs: 60000 # 1 minute + max: 500 # limit each IP to 500 requests per windowMs + +# The REST API server settings +# NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. +rest: + ssl: true + host: api7.dspace.org + port: 443 + # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: /server + +# Caching settings +cache: + # NOTE: how long should objects be cached for by default + msToLive: + default: 900000 # 15 minutes + control: max-age=60 # revalidate browser + autoSync: + defaultTime: 0 + maxBufferSize: 100 + timePerMethod: + PATCH: 3 # time in seconds + +# Authentication settings +auth: + # Authentication UI settings + ui: + # the amount of time before the idle warning is shown + timeUntilIdle: 900000 # 15 minutes + # the amount of time the user has to react after the idle warning is shown before they are logged out. + idleGracePeriod: 300000 # 5 minutes + # Authentication REST settings + rest: + # If the rest token expires in less than this amount of time, it will be refreshed automatically. + # This is independent from the idle warning. + timeLeftBeforeTokenRefresh: 120000 # 2 minutes + +# Form settings +form: + # NOTE: Map server-side validators to comparative Angular form validators + validatorMap: + required: required + regex: pattern + +# Notification settings +notifications: + rtl: false + position: + - top + - right + maxStack: 8 + # NOTE: after how many seconds notification is closed automatically. If set to zero notifications are not closed automatically + timeOut: 5000 # 5 second + clickToClose: true + # NOTE: 'fade' | 'fromTop' | 'fromRight' | 'fromBottom' | 'fromLeft' | 'rotate' | 'scale' + animate: scale + +# Submission settings +submission: + autosave: + # NOTE: which metadata trigger an autosave + metadata: [] + # NOTE: after how many time (milliseconds) submission is saved automatically + # eg. timer: 5 * (1000 * 60); // 5 minutes + timer: 0 + icons: + metadata: + # NOTE: example of configuration + # # NOTE: metadata name + # - name: dc.author + # # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + # style: fas fa-user + - name: dc.author + style: fas fa-user + # default configuration + - name: default + style: '' + authority: + confidence: + # NOTE: example of configuration + # # NOTE: confidence value + # - name: dc.author + # # NOTE: fontawesome (v5.x) icon classes and bootstrap utility classes can be used + # style: fa-user + - value: 600 + style: text-success + - value: 500 + style: text-info + - value: 400 + style: text-warning + # default configuration + - value: default + style: text-muted + +# Default Language in which the UI will be rendered if the user's browser language is not an active language +defaultLanguage: en + +# Languages. DSpace Angular holds a message catalog for each of the following languages. +# When set to active, users will be able to switch to the use of this language in the user interface. +languages: + - code: en + label: English + active: true + - code: cs + label: Čeština + active: true + - code: de + label: Deutsch + active: true + - code: es + label: Español + active: true + - code: fr + label: Français + active: true + - code: lv + label: Latviešu + active: true + - code: hu + label: Magyar + active: true + - code: nl + label: Nederlands + active: true + - code: pt-PT + label: Português + active: true + - code: pt-BR + label: Português do Brasil + active: true + - code: fi + label: Suomi + active: true + +# Browse-By Pages +browseBy: + # Amount of years to display using jumps of one year (current year - oneYearLimit) + oneYearLimit: 10 + # Limit for years to display using jumps of five years (current year - fiveYearLimit) + fiveYearLimit: 30 + # The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) + defaultLowerLimit: 1900 + # List of all the active Browse-By types + # Adding a type will activate their Browse-By page and add them to the global navigation menu, + # as well as community and collection pages + # Allowed fields and their purpose: + # id: The browse id to use for fetching info from the rest api + # type: The type of Browse-By page to display + # metadataField: The metadata-field used to create starts-with options (only necessary when the type is set to 'date') + types: + - id: title + type: title + - id: dateissued + type: date + metadataField: dc.date.issued + - id: author + type: metadata + - id: subject + type: metadata + +# Item Page Config +item: + edit: + undoTimeout: 10000 # 10 seconds + +# Collection Page Config +collection: + edit: + undoTimeout: 10000 # 10 seconds + +# Theme Config +themes: + # Add additional themes here. In the case where multiple themes match a route, the first one + # in this list will get priority. It is advisable to always have a theme that matches + # every route as the last one + # + # # A theme with a handle property will match the community, collection or item with the given + # # handle, and all collections and/or items within it + # - name: 'custom', + # handle: '10673/1233' + # + # # A theme with a regex property will match the route using a regular expression. If it + # # matches the route for a community or collection it will also apply to all collections + # # and/or items within it + # - name: 'custom', + # regex: 'collections\/e8043bc2.*' + # + # # A theme with a uuid property will match the community, collection or item with the given + # # ID, and all collections and/or items within it + # - name: 'custom', + # uuid: '0958c910-2037-42a9-81c7-dca80e3892b4' + # + # # The extends property specifies an ancestor theme (by name). Whenever a themed component is not found + # # in the current theme, its ancestor theme(s) will be checked recursively before falling back to default. + # - name: 'custom-A', + # extends: 'custom-B', + # # Any of the matching properties above can be used + # handle: '10673/34' + # + # - name: 'custom-B', + # extends: 'custom', + # handle: '10673/12' + # + # # A theme with only a name will match every route + # name: 'custom' + # + # # This theme will use the default bootstrap styling for DSpace components + # - name: BASE_THEME_NAME + # + - name: dspace + +# Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video'). +# For images, this enables a gallery viewer where you can zoom or page through images. +# For videos, this enables embedded video streaming +mediaViewer: + image: false + video: false diff --git a/config/config.yml b/config/config.yml new file mode 100644 index 0000000000..b5eecd112f --- /dev/null +++ b/config/config.yml @@ -0,0 +1,5 @@ +rest: + ssl: true + host: api7.dspace.org + port: 443 + nameSpace: /server diff --git a/docs/Configuration.md b/docs/Configuration.md index 82aaa42c40..d21a41e277 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,15 +1,15 @@ # Configuration -Default configuration file is located in `config/` folder. All configuration options should be listed in the default configuration file `src/config/default-app-config.ts`. Please do not change this file directly! To change the default configuration values, create local files that override the parameters you need to change. You can use `appConfig.json` as a starting point. +Default configuration file is located at `config/config.yml`. All configuration options should be listed in the default typescript file `src/config/default-app-config.ts`. Please do not change this file directly! To override the default configuration values, create local files that override the parameters you need to change. You can use `config.example.yml` as a starting point. -- Create a new `appConfig.(dev or development).json` file in `config/` for `development` environment; -- Create a new `appConfig.(prod or production).json` file in `config/` for `production` environment; +- Create a new `config.(dev or development).yml` file in `config/` for `development` environment; +- Create a new `config.(prod or production).yml` file in `config/` for `production` environment; Alternatively, create a desired app config file at an external location and set the path as environment variable `APP_CONFIG_PATH`. e.g. ``` -APP_CONFIG_PATH=/usr/local/dspace/config/appConfig.json +APP_CONFIG_PATH=/usr/local/dspace/config/config.yml ``` Configuration options can be overridden by setting environment variables. diff --git a/package.json b/package.json index e0b07b1040..097f9f21ab 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint": "ng lint", "lint-fix": "ng lint --fix=true", "e2e": "ng e2e", - "clean:dev:config": "rimraf src/assets/appConfig.json", + "clean:dev:config": "rimraf src/assets/config.json", "clean:coverage": "rimraf coverage", "clean:dist": "rimraf dist", "clean:doc": "rimraf doc", @@ -35,7 +35,8 @@ "build:mirador": "webpack --config webpack/webpack.mirador.config.ts", "merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts", "cypress:open": "cypress open", - "cypress:run": "cypress run" + "cypress:run": "cypress run", + "env:yaml": "ts-node --project ./tsconfig.ts-node.json scripts/env-to-yaml.ts" }, "browser": { "fs": false, @@ -90,6 +91,7 @@ "http-proxy-middleware": "^1.0.5", "https": "1.0.0", "js-cookie": "2.2.1", + "js-yaml": "^4.1.0", "json5": "^2.1.3", "jsonschema": "1.4.0", "jwt-decode": "^3.1.2", diff --git a/scripts/env-to-yaml.ts b/scripts/env-to-yaml.ts new file mode 100644 index 0000000000..47f876f20a --- /dev/null +++ b/scripts/env-to-yaml.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs'; +import * as yaml from 'js-yaml'; +import { join } from 'path'; + +const args = process.argv.slice(2); + +if (args[0] === undefined) { + console.log(`Usage:\n\tyarn env:yaml [relative path to environment.ts file] (optional relative path to write yaml file)\n`); + process.exit(0); +} + +const envFullPath = join(process.cwd(), args[0]); + +if (!fs.existsSync(envFullPath)) { + console.error(`Error:\n${envFullPath} does not exist\n`); + process.exit(1); +} + +try { + const env = require(envFullPath); + + const config = yaml.dump(env); + if (args[1]) { + const ymlFullPath = join(process.cwd(), args[1]); + fs.writeFileSync(ymlFullPath, config); + } else { + console.log(config); + } +} catch (e) { + console.error(e); +} + diff --git a/server.ts b/server.ts index 70d01e7710..da3b877bc1 100644 --- a/server.ts +++ b/server.ts @@ -58,7 +58,7 @@ const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : ' const cookieParser = require('cookie-parser'); -const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/appConfig.json')); +const appConfig: AppConfig = buildAppConfig(join(DIST_FOLDER, 'assets/config.json')); // extend environment with app config for server extendEnvironmentWithAppConfig(environment, appConfig); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2a7a885794..b1039f9c6d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -109,8 +109,6 @@ export class AppComponent implements OnInit, AfterViewInit { @Optional() private googleAnalyticsService: GoogleAnalyticsService, ) { - console.log(this.appConfig); - if (!isEqual(environment, this.appConfig)) { throw new Error('environment does not match app config!'); } diff --git a/src/assets/.gitignore b/src/assets/.gitignore index c25cf8c104..d344ba6b06 100644 --- a/src/assets/.gitignore +++ b/src/assets/.gitignore @@ -1 +1 @@ -appConfig.json +config.json diff --git a/src/config/config.server.ts b/src/config/config.server.ts index 5e8d8e1587..25a04c20a7 100644 --- a/src/config/config.server.ts +++ b/src/config/config.server.ts @@ -1,5 +1,6 @@ import * as colors from 'colors'; import * as fs from 'fs'; +import * as yaml from 'js-yaml'; import { join } from 'path'; import { AppConfig } from './app-config.interface'; @@ -44,8 +45,12 @@ const getEnvironment = (): Environment => { }; const getLocalConfigPath = (env: Environment) => { - // default to config/appConfig.json - let localConfigPath = join(CONFIG_PATH, 'appConfig.json'); + // default to config/config.yml + let localConfigPath = join(CONFIG_PATH, 'config.yml'); + + if (!fs.existsSync(localConfigPath)) { + localConfigPath = join(CONFIG_PATH, 'config.yaml'); + } // determine app config filename variations let envVariations; @@ -63,9 +68,16 @@ const getLocalConfigPath = (env: Environment) => { // check if any environment variations of app config exist for (const envVariation of envVariations) { - const envLocalConfigPath = join(CONFIG_PATH, `appConfig.${envVariation}.json`); + let envLocalConfigPath = join(CONFIG_PATH, `config.${envVariation}.yml`); if (fs.existsSync(envLocalConfigPath)) { localConfigPath = envLocalConfigPath; + break; + } else { + envLocalConfigPath = join(CONFIG_PATH, `config.${envVariation}.yaml`); + if (fs.existsSync(envLocalConfigPath)) { + localConfigPath = envLocalConfigPath; + break; + } } } @@ -76,7 +88,7 @@ const overrideWithConfig = (config: Config, pathToConfig: string) => { try { console.log(`Overriding app config with ${pathToConfig}`); const externalConfig = fs.readFileSync(pathToConfig, 'utf8'); - mergeConfig(config, JSON.parse(externalConfig)); + mergeConfig(config, yaml.load(externalConfig)); } catch (err) { console.error(err); } @@ -190,7 +202,7 @@ export const buildAppConfig = (destConfigPath?: string): AppConfig => { if (isNotEmpty(destConfigPath)) { fs.writeFileSync(destConfigPath, JSON.stringify(appConfig, null, 2)); - console.log(`Angular ${colors.bold('appConfig.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); + console.log(`Angular ${colors.bold('config.json')} file generated correctly at ${colors.bold(destConfigPath)} \n`); } return appConfig; diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index bceea29b96..c1bb275921 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -20,7 +20,17 @@ import { UniversalConfig } from './universal-config.interface'; export class DefaultAppConfig implements AppConfig { production = false; - // Angular Universal server settings. + // Angular Universal settings + universal: UniversalConfig = { + preboot: true, + async: true, + time: false + }; + + // NOTE: will log all redux actions and transfers in console + debug = false; + + // Angular Universal server settings // NOTE: these must be 'synced' with the 'dspace.ui.url' setting in your backend's local.cfg. ui: UIServerConfig = { ssl: false, @@ -36,7 +46,7 @@ export class DefaultAppConfig implements AppConfig { } }; - // The REST API server settings. + // The REST API server settings // NOTE: these must be 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. rest: ServerConfig = { ssl: false, @@ -164,16 +174,6 @@ export class DefaultAppConfig implements AppConfig { } }; - // Angular Universal settings - universal: UniversalConfig = { - preboot: true, - async: true, - time: false - }; - - // NOTE: will log all redux actions and transfers in console - debug = false; - // Default Language in which the UI will be rendered if the user's browser language is not an active language defaultLanguage = 'en'; diff --git a/src/main.browser.ts b/src/main.browser.ts index c72ca0cf31..de4276ea93 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -34,7 +34,7 @@ const main = () => { return bootstrap(); } else { - return fetch('assets/appConfig.json') + return fetch('assets/config.json') .then((response) => response.json()) .then((appConfig: AppConfig) => { diff --git a/webpack/webpack.browser.ts b/webpack/webpack.browser.ts index 134eecb7ed..a71d749347 100644 --- a/webpack/webpack.browser.ts +++ b/webpack/webpack.browser.ts @@ -10,7 +10,7 @@ module.exports = Object.assign({}, commonExports, { }, devServer: { before(app, server) { - buildAppConfig(join(process.cwd(), 'src/assets/appConfig.json')); + buildAppConfig(join(process.cwd(), 'src/assets/config.json')); } } }); diff --git a/yarn.lock b/yarn.lock index db63c66040..6fe2896b20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2753,6 +2753,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + aria-query@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" @@ -8012,6 +8017,13 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" From 2bf880b21603879ba295ac48ca524968179b02b1 Mon Sep 17 00:00:00 2001 From: William Welling Date: Wed, 8 Dec 2021 19:31:05 -0600 Subject: [PATCH 27/59] correct typo in comment --- src/config/config.util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.util.ts b/src/config/config.util.ts index 6c3b9e0f7a..c45282269c 100644 --- a/src/config/config.util.ts +++ b/src/config/config.util.ts @@ -35,7 +35,7 @@ const mergeConfig = (destinationConfig: any, sourceConfig: AppConfig): void => { }; /** - * Get default them config from environment. + * Get default theme config from environment. * * @returns default theme config */ From 5acc29e49856608c3ffa0accdb8ed89ed2aca80b Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 13 Dec 2021 10:51:16 -0800 Subject: [PATCH 28/59] Updated Mirador dependencies. --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 236845da1a..28ffd3d970 100644 --- a/package.json +++ b/package.json @@ -110,9 +110,9 @@ "jwt-decode": "^3.1.2", "klaro": "^0.7.10", "lodash": "^4.17.21", - "mirador": "^3.0.0", + "mirador": "^3.3.0", "mirador-dl-plugin": "^0.13.0", - "mirador-share-plugin": "^0.10.0", + "mirador-share-plugin": "^0.11.0", "moment": "^2.29.1", "morgan": "^1.10.0", "ng-mocks": "11.11.2", @@ -125,8 +125,6 @@ "nouislider": "^14.6.3", "pem": "1.14.4", "postcss-cli": "^8.3.0", - "react": "^16.14.0", - "react-dom": "^16.14.0", "reflect-metadata": "^0.1.13", "rxjs": "^6.6.3", "sortablejs": "1.13.0", @@ -189,6 +187,8 @@ "protractor": "^7.0.0", "protractor-istanbul-plugin": "2.0.0", "raw-loader": "0.5.1", + "react": "^16.14.0", + "react-dom": "^16.14.0", "rimraf": "^3.0.2", "rxjs-spy": "^7.5.3", "sass-resources-loader": "^2.1.1", @@ -204,4 +204,4 @@ "webpack-cli": "^4.2.0", "webpack-dev-server": "^4.5.0" } -} \ No newline at end of file +} From b1c3967a5b70c3f6d0b4691557012ece55ee3a6a Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 13 Dec 2021 11:32:37 -0800 Subject: [PATCH 29/59] Added lockfile. --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index fdb758af01..f467fb9840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9121,12 +9121,12 @@ mirador-dl-plugin@^0.13.0: resolved "https://registry.yarnpkg.com/mirador-dl-plugin/-/mirador-dl-plugin-0.13.0.tgz#9a6cb0fa3c566a2a1ebe1ad9caa1ff590ff22689" integrity sha512-I/6etIvpTtO1zgjxx2uEUFoyB9NxQ43JWg8CMkKmZqblW7AAeFqRn1/zUlQH7N8KFZft9Rah6D8qxtuNAo9jmA== -mirador-share-plugin@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/mirador-share-plugin/-/mirador-share-plugin-0.10.0.tgz#82cde27faedc440fab648db137e62849d6542420" - integrity sha512-hC9hG0H04WAR6JNfLDnQICtxwWV3K+cmqnArtOvAIGGnbgXWs5tmQyfdY55z05jzbeL40rd7z1K094hHV3R4WQ== +mirador-share-plugin@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/mirador-share-plugin/-/mirador-share-plugin-0.11.0.tgz#13e2f654e38839044382acad42d9329e91a8cd5e" + integrity sha512-fHcdDXyrtfy5pn1zdQNX9BvE5Tjup66eQwyNippE5PMaP8ImUcrFaSL+mStdn+v6agsHcsdRqLhseZ0XWgEuAw== -mirador@^3.0.0: +mirador@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/mirador/-/mirador-3.3.0.tgz#7a957a1db1a5388b2b8cafab00db4eb9f97557b9" integrity sha512-BmGfRnWJ45B+vtiAwcFT7n9nKialfejE9UvuUK0NorO37ShArpsKr3yVSD4jQASwSR4DRRpPEG21jOk4WN7H3w== From 0afa7c5bab0249400aaee424338c32692880a574 Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 14 Dec 2021 18:01:34 -0600 Subject: [PATCH 30/59] update readme examples to yaml --- docs/Configuration.md | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index d21a41e277..af0efc1f02 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -18,15 +18,13 @@ Configuration options can be overridden by setting environment variables. When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`. To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above): -``` -{ - "ui": { - "ssl": false, - "host": "localhost", - "port": 4000, - "nameSpace": "/" - } -} + +```yaml +ui: + ssl: false + host: localhost + port: 4000 + nameSpace: / ``` Alternately you can set the following environment variables. If any of these are set, it will override all configuration files: @@ -47,14 +45,12 @@ or ## DSpace's REST endpoint dspace-angular connects to your DSpace installation by using its REST endpoint. To do so, you have to define the ip address, port and if ssl should be enabled. You can do this in a configuration file (see above) by adding the following options: -``` -{ - "rest": { - "ssl": true, - "host": "api7.dspace.org", - "port": 443, - "nameSpace": "/server" - } +```yaml +rest: + ssl: true + host: api7.dspace.org + port: 443 + nameSpace: /server } ``` From c7321f9a22a439398fee4c97f94202ed77cc293b Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 14 Dec 2021 18:02:27 -0600 Subject: [PATCH 31/59] update script comment --- scripts/test-rest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-rest.ts b/scripts/test-rest.ts index 00455d69b5..aa3b64f62b 100644 --- a/scripts/test-rest.ts +++ b/scripts/test-rest.ts @@ -7,7 +7,7 @@ import { buildAppConfig } from '../src/config/config.server'; const appConfig: AppConfig = buildAppConfig(); /** - * Script to test the connection with the configured REST API (in the 'rest' settings of your appConfig.*.json) + * Script to test the connection with the configured REST API (in the 'rest' settings of your config.*.yaml) * * This script is useful to test for any Node.js connection issues with your REST API. * From b820794790eb514a2ef6bb0e69dc893917ea9894 Mon Sep 17 00:00:00 2001 From: William Welling Date: Tue, 14 Dec 2021 18:11:37 -0600 Subject: [PATCH 32/59] comment env-to-yaml script --- scripts/env-to-yaml.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/env-to-yaml.ts b/scripts/env-to-yaml.ts index 47f876f20a..edcdfd90b4 100644 --- a/scripts/env-to-yaml.ts +++ b/scripts/env-to-yaml.ts @@ -2,8 +2,15 @@ import * as fs from 'fs'; import * as yaml from 'js-yaml'; import { join } from 'path'; -const args = process.argv.slice(2); +/** + * Script to help convert previous version environment.*.ts to yaml. + * + * Usage (see package.json): + * + * yarn env:yaml [relative path to environment.ts file] (optional relative path to write yaml file) * + */ +const args = process.argv.slice(2); if (args[0] === undefined) { console.log(`Usage:\n\tyarn env:yaml [relative path to environment.ts file] (optional relative path to write yaml file)\n`); process.exit(0); From a1578303fa6f2939ecb26f3799b21781461a5164 Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 15 Dec 2021 16:15:06 +0100 Subject: [PATCH 33/59] 85993: Browse by from rest for menu and comcol page browse by --- src/app/navbar/navbar.component.ts | 72 +++++++++++-------- .../comcol-page-browse-by.component.ts | 41 +++++++---- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index e741cea285..29541c77a0 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -6,7 +6,11 @@ import { MenuID, MenuItemType } from '../shared/menu/initial-menus-state'; import { TextMenuItemModel } from '../shared/menu/menu-item/models/text.model'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { HostWindowService } from '../shared/host-window.service'; -import { environment } from '../../environments/environment'; +import { BrowseService } from '../core/browse/browse.service'; +import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { PaginatedList } from '../core/data/paginated-list.model'; +import { BrowseDefinition } from '../core/shared/browse-definition.model'; +import { RemoteData } from '../core/data/remote-data'; /** * Component representing the public navbar @@ -26,7 +30,8 @@ export class NavbarComponent extends MenuComponent { constructor(protected menuService: MenuService, protected injector: Injector, - public windowService: HostWindowService + public windowService: HostWindowService, + public browseService: BrowseService ) { super(menuService, injector); } @@ -52,37 +57,44 @@ export class NavbarComponent extends MenuComponent { text: `menu.section.browse_global_communities_and_collections`, link: `/community-list` } as LinkMenuItemModel - }, - /* News */ - { - id: 'browse_global', - active: false, - visible: true, - index: 1, - model: { - type: MenuItemType.TEXT, - text: 'menu.section.browse_global' - } as TextMenuItemModel, - }, + } ]; // Read the different Browse-By types from config and add them to the browse menu - const types = environment.browseBy.types; - types.forEach((typeConfig) => { - menuList.push({ - id: `browse_global_by_${typeConfig.id}`, - parentID: 'browse_global', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: `menu.section.browse_global_by_${typeConfig.id}`, - link: `/browse/${typeConfig.id}` - } as LinkMenuItemModel + this.browseService.getBrowseDefinitions() + .pipe(getFirstCompletedRemoteData>()) + .subscribe((browseDefListRD: RemoteData>) => { + if (browseDefListRD.hasSucceeded) { + browseDefListRD.payload.page.forEach((browseDef: BrowseDefinition) => { + menuList.push({ + id: `browse_global_by_${browseDef.id}`, + parentID: 'browse_global', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: `menu.section.browse_global_by_${browseDef.id}`, + link: `/browse/${browseDef.id}` + } as LinkMenuItemModel + }); + }); + menuList.push( + /* Browse */ + { + id: 'browse_global', + active: false, + visible: true, + index: 1, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.browse_global' + } as TextMenuItemModel, + },) + } + menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { + shouldPersistOnRouteChange: true + }))); }); - }); - menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { - shouldPersistOnRouteChange: true - }))); + } } diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index 01912dbcaa..5dec1e27f8 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -6,6 +6,11 @@ import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interf import { environment } from '../../../environments/environment'; import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths'; import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { RemoteData } from '../../core/data/remote-data'; +import { BrowseService } from '../../core/browse/browse.service'; export interface ComColPageNavOption { id: string; @@ -40,30 +45,38 @@ export class ComcolPageBrowseByComponent implements OnInit { constructor( private route: ActivatedRoute, - private router: Router) { + private router: Router, + public browseService: BrowseService + ) { } ngOnInit(): void { - this.allOptions = environment.browseBy.types - .map((config: BrowseByTypeConfig) => ({ - id: config.id, - label: `browse.comcol.by.${config.id}`, - routerLink: `/browse/${config.id}`, - params: { scope: this.id } - })); + this.browseService.getBrowseDefinitions() + .pipe(getFirstCompletedRemoteData>()) + .subscribe((browseDefListRD: RemoteData>) => { + if (browseDefListRD.hasSucceeded) { + this.allOptions = browseDefListRD.payload.page + .map((config: BrowseDefinition) => ({ + id: config.id, + label: `browse.comcol.by.${config.id}`, + routerLink: `/browse/${config.id}`, + params: { scope: this.id } + })); + } + }); if (this.contentType === 'collection') { - this.allOptions = [ { + this.allOptions = [{ id: this.id, label: 'collection.page.browse.recent.head', routerLink: getCollectionPageRoute(this.id) - }, ...this.allOptions ]; + }, ...this.allOptions]; } else if (this.contentType === 'community') { this.allOptions = [{ - id: this.id, - label: 'community.all-lists.head', - routerLink: getCommunityPageRoute(this.id) - }, ...this.allOptions ]; + id: this.id, + label: 'community.all-lists.head', + routerLink: getCommunityPageRoute(this.id) + }, ...this.allOptions]; } this.currentOptionId$ = this.route.params.pipe( From 32ae686e36030f4ca9a6ced4a487ab39b24abadb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 15 Dec 2021 14:24:23 -0600 Subject: [PATCH 34/59] Syntax fix, add trailing quote --- src/assets/i18n/de.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 268d8792c8..bb719f202e 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -2182,7 +2182,7 @@ "item.edit.bitstreams.headers.name": "Name", // "item.edit.bitstreams.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "item.edit.bitstreams.notifications.discarded.content": "Ihre Änderungen wurden verworfen. Um Ihre Änderungen wiederherzustellen, klicken Sie auf die Schaltfläche 'Rückgängig', + "item.edit.bitstreams.notifications.discarded.content": "Ihre Änderungen wurden verworfen. Um Ihre Änderungen wiederherzustellen, klicken Sie auf die Schaltfläche 'Rückgängig'", // "item.edit.bitstreams.notifications.discarded.title": "Changes discarded", "item.edit.bitstreams.notifications.discarded.title": "Änderungen verworfen", From 21fcc9dde8c4b9a94f0d186053b6dd95bbfcc10b Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 16 Dec 2021 00:25:59 -0600 Subject: [PATCH 35/59] prefix dspace environment variables --- README.md | 29 ++++++++++++++++++----- docs/Configuration.md | 19 +++++---------- src/config/config.server.ts | 47 +++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index d21d54ac33..b4ae6c884b 100644 --- a/README.md +++ b/README.md @@ -120,11 +120,6 @@ DSPACE_HOST # The host name of the angular application DSPACE_PORT # The port number of the angular application DSPACE_NAMESPACE # The namespace of the angular application DSPACE_SSL # Whether the angular application uses SSL [true/false] - -DSPACE_REST_HOST # The host name of the REST application -DSPACE_REST_PORT # The port number of the REST application -DSPACE_REST_NAMESPACE # The namespace of the REST application -DSPACE_REST_SSL # Whether the angular REST uses SSL [true/false] ``` All other settings can be set using the following convention for naming the environment variables: @@ -134,14 +129,36 @@ All other settings can be set using the following convention for naming the envi e.g. -``` +```bash +# The host name of the REST application +dspace.rest.host => DSPACE_REST_HOST + +# The port number of the REST application +dspace.rest.port => DSPACE_REST_PORT + +# The namespace of the REST application +dspace.rest.nameSpace => DSPACE_REST_NAMESPACE + +# Whether the angular REST uses SSL [true/false] +dspace.rest.ssl => DSPACE_REST_SSL + cache.msToLive.default => CACHE_MSTOLIVE_DEFAULT auth.ui.timeUntilIdle => AUTH_UI_TIMEUNTILIDLE ``` +The equavelant to the non-conventional legacy settings: + +```bash +DSPACE_UI_HOST => DSPACE_HOST +DSPACE_UI_PORT => DSPACE_PORT +DSPACE_UI_NAMESPACE => DSPACE_NAMESPACE +DSPACE_UI_SSL => DSPACE_SSL +``` + The same settings can also be overwritten by setting system environment variables instead, E.g.: ```bash export DSPACE_HOST=api7.dspace.org +export DSPACE_UI_PORT=4200 ``` The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`** diff --git a/docs/Configuration.md b/docs/Configuration.md index af0efc1f02..193ca0f57d 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -5,11 +5,11 @@ Default configuration file is located at `config/config.yml`. All configuration - Create a new `config.(dev or development).yml` file in `config/` for `development` environment; - Create a new `config.(prod or production).yml` file in `config/` for `production` environment; -Alternatively, create a desired app config file at an external location and set the path as environment variable `APP_CONFIG_PATH`. +Alternatively, create a desired app config file at an external location and set the path as environment variable `DSPACE_APP_CONFIG_PATH`. e.g. ``` -APP_CONFIG_PATH=/usr/local/dspace/config/config.yml +DSPACE_APP_CONFIG_PATH=/usr/local/dspace/config/config.yml ``` Configuration options can be overridden by setting environment variables. @@ -36,10 +36,10 @@ Alternately you can set the following environment variables. If any of these are ``` or ``` - UI_SSL=true - UI_HOST=localhost - UI_PORT=4000 - UI_NAMESPACE=/ + DSPACE_UI_SSL=true + DSPACE_UI_HOST=localhost + DSPACE_UI_PORT=4000 + DSPACE_UI_NAMESPACE=/ ``` ## DSpace's REST endpoint @@ -61,13 +61,6 @@ Alternately you can set the following environment variables. If any of these are DSPACE_REST_PORT=443 DSPACE_REST_NAMESPACE=/server ``` -or -``` - REST_SSL=true - REST_HOST=api7.dspace.org - REST_PORT=443 - REST_NAMESPACE=/server -``` ## Environment variable naming convention diff --git a/src/config/config.server.ts b/src/config/config.server.ts index 25a04c20a7..36d7d1f9d8 100644 --- a/src/config/config.server.ts +++ b/src/config/config.server.ts @@ -14,6 +14,14 @@ const CONFIG_PATH = join(process.cwd(), 'config'); type Environment = 'production' | 'development' | 'test'; +const DSPACE = (key: string): string => { + return `DSPACE_${key}`; +}; + +const ENV = (key: string, prefix = false): any => { + return prefix ? process.env[DSPACE(key)] : process.env[key]; +}; + const getBooleanFromString = (variable: string): boolean => { return variable === 'true' || variable === '1'; }; @@ -24,8 +32,8 @@ const getNumberFromString = (variable: string): number => { const getEnvironment = (): Environment => { let environment: Environment = 'development'; - if (isNotEmpty(process.env.NODE_ENV)) { - switch (process.env.NODE_ENV) { + if (isNotEmpty(ENV('NODE_ENV'))) { + switch (ENV('NODE_ENV')) { case 'prod': case 'production': environment = 'production'; @@ -37,7 +45,7 @@ const getEnvironment = (): Environment => { case 'development': break; default: - console.warn(`Unknown NODE_ENV ${process.env.NODE_ENV}. Defaulting to development`); + console.warn(`Unknown NODE_ENV ${ENV('NODE_ENV')}. Defaulting to development`); } } @@ -102,19 +110,20 @@ const overrideWithEnvironment = (config: Config, key: string = '') => { if (typeof innerConfig === 'object') { overrideWithEnvironment(innerConfig, variable); } else { - if (isNotEmpty(process.env[variable])) { - console.log(`Applying environment variable ${variable} with value ${process.env[variable]}`); + const value = ENV(variable, true); + if (isNotEmpty(value)) { + console.log(`Applying environment variable ${DSPACE(variable)} with value ${value}`); switch (typeof innerConfig) { case 'number': - config[property] = getNumberFromString(process.env[variable]); + config[property] = getNumberFromString(value); break; case 'boolean': - config[property] = getBooleanFromString(process.env[variable]); + config[property] = getBooleanFromString(value); break; case 'string': - config[property] = process.env[variable]; + config[property] = value; default: - console.warn(`Unsupported environment variable type ${typeof innerConfig} ${variable}`); + console.warn(`Unsupported environment variable type ${typeof innerConfig} ${DSPACE(variable)}`); } } } @@ -122,6 +131,8 @@ const overrideWithEnvironment = (config: Config, key: string = '') => { } }; + + const buildBaseUrl = (config: ServerConfig): void => { config.baseUrl = [ config.ssl ? 'https://' : 'http://', @@ -168,7 +179,7 @@ export const buildAppConfig = (destConfigPath?: string): AppConfig => { } // override with external config if specified by environment variable `APP_CONFIG_PATH` - const externalConfigPath = process.env.APP_CONFIG_PATH; + const externalConfigPath = ENV('APP_CONFIG_PATH', true); if (isNotEmpty(externalConfigPath)) { if (fs.existsSync(externalConfigPath)) { overrideWithConfig(appConfig, externalConfigPath); @@ -181,16 +192,16 @@ export const buildAppConfig = (destConfigPath?: string): AppConfig => { overrideWithEnvironment(appConfig); // apply existing non convention UI environment variables - appConfig.ui.host = isNotEmpty(process.env.DSPACE_HOST) ? process.env.DSPACE_HOST : appConfig.ui.host; - appConfig.ui.port = isNotEmpty(process.env.DSPACE_PORT) ? getNumberFromString(process.env.DSPACE_PORT) : appConfig.ui.port; - appConfig.ui.nameSpace = isNotEmpty(process.env.DSPACE_NAMESPACE) ? process.env.DSPACE_NAMESPACE : appConfig.ui.nameSpace; - appConfig.ui.ssl = isNotEmpty(process.env.DSPACE_SSL) ? getBooleanFromString(process.env.DSPACE_SSL) : appConfig.ui.ssl; + appConfig.ui.host = isNotEmpty(ENV('HOST', true)) ? ENV('HOST', true) : appConfig.ui.host; + appConfig.ui.port = isNotEmpty(ENV('PORT', true)) ? getNumberFromString(ENV('PORT', true)) : appConfig.ui.port; + appConfig.ui.nameSpace = isNotEmpty(ENV('NAMESPACE', true)) ? ENV('NAMESPACE', true) : appConfig.ui.nameSpace; + appConfig.ui.ssl = isNotEmpty(ENV('SSL', true)) ? getBooleanFromString(ENV('SSL', true)) : appConfig.ui.ssl; // apply existing non convention REST environment variables - appConfig.rest.host = isNotEmpty(process.env.DSPACE_REST_HOST) ? process.env.DSPACE_REST_HOST : appConfig.rest.host; - appConfig.rest.port = isNotEmpty(process.env.DSPACE_REST_PORT) ? getNumberFromString(process.env.DSPACE_REST_PORT) : appConfig.rest.port; - appConfig.rest.nameSpace = isNotEmpty(process.env.DSPACE_REST_NAMESPACE) ? process.env.DSPACE_REST_NAMESPACE : appConfig.rest.nameSpace; - appConfig.rest.ssl = isNotEmpty(process.env.DSPACE_REST_SSL) ? getBooleanFromString(process.env.DSPACE_REST_SSL) : appConfig.rest.ssl; + appConfig.rest.host = isNotEmpty(ENV('REST_HOST', true)) ? ENV('REST_HOST', true) : appConfig.rest.host; + appConfig.rest.port = isNotEmpty(ENV('REST_PORT', true)) ? getNumberFromString(ENV('REST_PORT', true)) : appConfig.rest.port; + appConfig.rest.nameSpace = isNotEmpty(ENV('REST_NAMESPACE', true)) ? ENV('REST_NAMESPACE', true) : appConfig.rest.nameSpace; + appConfig.rest.ssl = isNotEmpty(ENV('REST_SSL', true)) ? getBooleanFromString(ENV('REST_SSL', true)) : appConfig.rest.ssl; // apply build defined production appConfig.production = env === 'production'; From 9801ae341409fd0024ccf774f2167f03901db4f3 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 16 Dec 2021 10:56:29 +0100 Subject: [PATCH 36/59] update legacy relative links to angular 11 format --- .../shared/resource-policies/resource-policies.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/resource-policies/resource-policies.component.ts b/src/app/shared/resource-policies/resource-policies.component.ts index 30bd7ce45b..94c928438a 100644 --- a/src/app/shared/resource-policies/resource-policies.component.ts +++ b/src/app/shared/resource-policies/resource-policies.component.ts @@ -281,7 +281,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy { * Redirect to resource policy creation page */ redirectToResourcePolicyCreatePage(): void { - this.router.navigate([`../create`], { + this.router.navigate([`./create`], { relativeTo: this.route, queryParams: { policyTargetId: this.resourceUUID, @@ -296,7 +296,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy { * @param policy The resource policy */ redirectToResourcePolicyEditPage(policy: ResourcePolicy): void { - this.router.navigate([`../edit`], { + this.router.navigate([`./edit`], { relativeTo: this.route, queryParams: { policyId: policy.id From e7f0921e6ed52ec25d60f70c61d19cddcfd2a656 Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 14 Dec 2021 09:30:38 +0100 Subject: [PATCH 37/59] 85862: Handling very long strings for metadata/files on item page, edit metadata/bitstreams page --- .../item-edit-bitstream.component.html | 6 ++++-- .../edit-in-place-field.component.html | 2 +- .../metadata-uri-values.component.html | 2 +- .../metadata-values.component.html | 2 +- .../file-download-link.component.html | 2 +- src/styles/_global-styles.scss | 18 ++++++++++++++++++ 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html index 62014f06bd..5794288018 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html @@ -1,8 +1,10 @@
-
- {{ bitstreamName }} +
+ + {{ bitstreamName }} +
diff --git a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html index fe46906f47..f5543af971 100644 --- a/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html +++ b/src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html @@ -26,7 +26,7 @@
- {{metadata?.value}} + {{metadata?.value}}