From d3466c3e8215dc0012b1e3f063aafde9ecf67de9 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 12 May 2021 17:59:11 +0200 Subject: [PATCH 01/84] Fix issue where uploaded files disappear --- src/app/submission/form/submission-form.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index 8df0ab16587..6d4ddb4ca08 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -122,7 +122,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { * Initialize all instance variables and retrieve form configuration */ ngOnChanges(changes: SimpleChanges) { - if (this.collectionId && this.submissionId) { + if ((changes.collectionId && this.collectionId) && (changes.submissionId && this.submissionId)) { this.isActive = true; // retrieve submission's section list From d06b76af3f850ec81b599798b2076b7b37989d14 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 19 May 2021 15:04:12 +0200 Subject: [PATCH 02/84] Fix issue with patching value with a date --- .../json-patch/builder/json-patch-operations-builder.ts | 4 +++- src/app/shared/date.util.ts | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/core/json-patch/builder/json-patch-operations-builder.ts b/src/app/core/json-patch/builder/json-patch-operations-builder.ts index ced37508343..d3896c4a6c2 100644 --- a/src/app/core/json-patch/builder/json-patch-operations-builder.ts +++ b/src/app/core/json-patch/builder/json-patch-operations-builder.ts @@ -9,7 +9,7 @@ import { import { JsonPatchOperationPathObject } from './json-patch-operation-path-combiner'; import { Injectable } from '@angular/core'; import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util'; -import { dateToISOFormat } from '../../../shared/date.util'; +import { dateToISOFormat, dateToString, isNgbDateStruct } from '../../../shared/date.util'; import { VocabularyEntry } from '../../submission/vocabularies/models/vocabulary-entry.model'; import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model'; @@ -136,6 +136,8 @@ export class JsonPatchOperationsBuilder { operationValue = new FormFieldMetadataValueObject(value.value, value.language); } else if (value.hasOwnProperty('authority')) { operationValue = new FormFieldMetadataValueObject(value.value, value.language, value.authority); + } else if (isNgbDateStruct(value)) { + operationValue = new FormFieldMetadataValueObject(dateToString(value)); } else if (value.hasOwnProperty('value')) { operationValue = new FormFieldMetadataValueObject(value.value); } else { diff --git a/src/app/shared/date.util.ts b/src/app/shared/date.util.ts index 063820784c9..44afdd10a40 100644 --- a/src/app/shared/date.util.ts +++ b/src/app/shared/date.util.ts @@ -3,7 +3,7 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; import { isObject } from 'lodash'; import * as moment from 'moment'; -import { isNull } from './empty.util'; +import { isNull, isUndefined } from './empty.util'; /** * Returns true if the passed value is a NgbDateStruct. @@ -27,8 +27,9 @@ export function isNgbDateStruct(value: object): boolean { * @return string * the formatted date */ -export function dateToISOFormat(date: Date | NgbDateStruct): string { - const dateObj: Date = (date instanceof Date) ? date : ngbDateStructToDate(date); +export function dateToISOFormat(date: Date | NgbDateStruct | string): string { + const dateObj: Date = (date instanceof Date) ? date : + ((typeof date === 'string') ? ngbDateStructToDate(stringToNgbDateStruct(date)) : ngbDateStructToDate(date)); let year = dateObj.getFullYear().toString(); let month = (dateObj.getMonth() + 1).toString(); @@ -80,7 +81,7 @@ export function stringToNgbDateStruct(date: string): NgbDateStruct { * the NgbDateStruct object */ export function dateToNgbDateStruct(date?: Date): NgbDateStruct { - if (isNull(date)) { + if (isNull(date) || isUndefined(date)) { date = new Date(); } From e0edcd64d2dcedd695ae2634f845a58b1f54d7f7 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 19 May 2021 15:35:37 +0200 Subject: [PATCH 03/84] Fix wrong visualization of bitstream access condition form within submission form --- src/app/shared/mocks/submission.mock.ts | 154 +++++++++--------- .../section-upload-file-edit.component.scss | 6 + .../section-upload-file-edit.component.ts | 8 +- .../edit/section-upload-file-edit.model.ts | 29 ++-- .../file/section-upload-file.component.ts | 1 + 5 files changed, 110 insertions(+), 88 deletions(-) create mode 100644 src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.scss diff --git a/src/app/shared/mocks/submission.mock.ts b/src/app/shared/mocks/submission.mock.ts index 1ee097af719..eaebb38df84 100644 --- a/src/app/shared/mocks/submission.mock.ts +++ b/src/app/shared/mocks/submission.mock.ts @@ -1519,83 +1519,87 @@ export const mockFileFormData = { }, accessConditions: [ { - name: [ - { - value: 'openaccess', - language: null, - authority: null, - display: 'openaccess', - confidence: -1, - place: 0, - otherInformation: null - } - ], - } - , + accessConditionGroup: { + name: [ + { + value: 'openaccess', + language: null, + authority: null, + display: 'openaccess', + confidence: -1, + place: 0, + otherInformation: null + } + ], + }, + }, { - name: [ - { - value: 'lease', - language: null, - authority: null, - display: 'lease', - confidence: -1, - place: 0, - otherInformation: null - } - ], - endDate: [ - { - value: { - year: 2019, - month: 1, - day: 16 - }, - language: null, - authority: null, - display: { - year: 2019, - month: 1, - day: 16 - }, - confidence: -1, - place: 0, - otherInformation: null - } - ], - } - , + accessConditionGroup:{ + name: [ + { + value: 'lease', + language: null, + authority: null, + display: 'lease', + confidence: -1, + place: 0, + otherInformation: null + } + ], + endDate: [ + { + value: { + year: 2019, + month: 1, + day: 16 + }, + language: null, + authority: null, + display: { + year: 2019, + month: 1, + day: 16 + }, + confidence: -1, + place: 0, + otherInformation: null + } + ], + } + }, { - name: [ - { - value: 'embargo', - language: null, - authority: null, - display: 'lease', - confidence: -1, - place: 0, - otherInformation: null - } - ], - startDate: [ - { - value: { - year: 2019, - month: 1, - day: 16 - }, - language: null, - authority: null, - display: { - year: 2019, - month: 1, - day: 16 - }, - confidence: -1, - place: 0, - otherInformation: null - } - ], + accessConditionGroup: { + name: [ + { + value: 'embargo', + language: null, + authority: null, + display: 'lease', + confidence: -1, + place: 0, + otherInformation: null + } + ], + startDate: [ + { + value: { + year: 2019, + month: 1, + day: 16 + }, + language: null, + authority: null, + display: { + year: 2019, + month: 1, + day: 16 + }, + confidence: -1, + place: 0, + otherInformation: null + } + ], + } } ] }; diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.scss b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.scss new file mode 100644 index 00000000000..b443db711bb --- /dev/null +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.scss @@ -0,0 +1,6 @@ + +::ng-deep .access-condition-group { + position: relative; + top: -2.3rem; + margin-bottom: -2.3rem; +} 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 512453d84e3..cfece7a5fe9 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 @@ -18,6 +18,8 @@ import { import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service'; import { + BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG, + BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG, BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG, @@ -43,6 +45,7 @@ import { FormComponent } from '../../../../../shared/form/form.component'; */ @Component({ selector: 'ds-submission-section-upload-file-edit', + styleUrls: ['./section-upload-file-edit.component.scss'], templateUrl: './section-upload-file-edit.component.html', }) export class SubmissionSectionUploadFileEditComponent implements OnChanges { @@ -209,8 +212,9 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges { const startDate = new DynamicDatePickerModel(startDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT); const endDate = new DynamicDatePickerModel(endDateConfig, BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT); - - return [type, startDate, endDate]; + 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 diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts index 096954659ed..300a4b461fa 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts @@ -15,12 +15,24 @@ export const BITSTREAM_METADATA_FORM_GROUP_CONFIG: DynamicFormGroupModelConfig = export const BITSTREAM_METADATA_FORM_GROUP_LAYOUT: DynamicFormControlLayout = { element: { container: 'form-group', - label: 'col-form-label' + label: 'col-form-label' }, grid: { label: 'col-sm-3' } }; +export const BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG: DynamicFormGroupModelConfig = { + id: 'accessConditionGroup', + group: [] +}; + +export const BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT: DynamicFormControlLayout = { + element: { + host: 'form-group flex-fill access-condition-group', + container: 'pl-1 pr-1', + control: 'form-row ' + } +}; export const BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG: DynamicFormArrayModelConfig = { id: 'accessConditions', @@ -28,7 +40,7 @@ export const BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_CONFIG: DynamicFormArrayMode }; export const BITSTREAM_ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT: DynamicFormControlLayout = { grid: { - group: 'form-row' + group: 'form-row pt-4', } }; @@ -39,11 +51,8 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG: DynamicSelectModelConf }; export const BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT: DynamicFormControlLayout = { element: { - container: 'p-0', - label: 'col-form-label' - }, - grid: { - host: 'col-md-10' + host: 'col-12', + label: 'col-form-label name-label' } }; @@ -70,11 +79,10 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePicke }; export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT: DynamicFormControlLayout = { element: { - container: 'p-0', label: 'col-form-label' }, grid: { - host: 'col-md-4' + host: 'col-6' } }; @@ -101,10 +109,9 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_CONFIG: DynamicDatePickerM }; export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT: DynamicFormControlLayout = { element: { - container: 'p-0', label: 'col-form-label' }, grid: { - host: 'col-md-4' + host: 'col-6' } }; 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 5a97140a703..d4c901b2903 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 @@ -255,6 +255,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { }); const accessConditionsToSave = []; formData.accessConditions + .map((accessConditions) => accessConditions.accessConditionGroup) .filter((accessCondition) => isNotEmpty(accessCondition)) .forEach((accessCondition) => { let accessConditionOpt; From e18c66d6888e4c5eeb481e64879902d1dc843b58 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 14 May 2021 19:14:56 +0200 Subject: [PATCH 04/84] Fix issue with patch operations related to repeatable fields --- .../form/section-form-operations.service.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index a1bb99e3cdc..8aef798cbca 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -6,7 +6,8 @@ import { DYNAMIC_FORM_CONTROL_TYPE_GROUP, DynamicFormArrayGroupModel, DynamicFormControlEvent, - DynamicFormControlModel, isDynamicFormControlEvent + DynamicFormControlModel, + isDynamicFormControlEvent } from '@ng-dynamic-forms/core'; import { hasValue, isNotEmpty, isNotNull, isNotUndefined, isNull, isUndefined } from '../../../shared/empty.util'; @@ -299,7 +300,7 @@ export class SectionFormOperationsService { if (event.context && event.context instanceof DynamicFormArrayGroupModel) { // Model is a DynamicRowArrayModel - this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context); + this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context, previousValue); return; } @@ -368,7 +369,7 @@ export class SectionFormOperationsService { if (event.context && event.context instanceof DynamicFormArrayGroupModel) { // Model is a DynamicRowArrayModel - this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context); + this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context, previousValue); return; } @@ -498,23 +499,37 @@ export class SectionFormOperationsService { event: DynamicFormControlEvent, previousValue: FormFieldPreviousValueObject) { - return this.handleArrayGroupPatch(pathCombiner, event.$event, (event as any).$event.arrayModel); + return this.handleArrayGroupPatch(pathCombiner, event.$event, (event as any).$event.arrayModel, previousValue); } /** * Specific patch handler for a DynamicRowArrayModel. * Configure a Patch ADD with the current array value. * @param pathCombiner + * the [[JsonPatchOperationPathCombiner]] object for the specified operation * @param event + * the [[DynamicFormControlEvent]] for the specified operation * @param model + * the [[DynamicRowArrayModel]] model + * @param previousValue + * the [[FormFieldPreviousValueObject]] for the specified operation */ private handleArrayGroupPatch(pathCombiner: JsonPatchOperationPathCombiner, event, - model: DynamicRowArrayModel) { + model: DynamicRowArrayModel, + previousValue: FormFieldPreviousValueObject) { + const arrayValue = this.formBuilder.getValueFromModel([model]); - const segmentedPath2 = this.getFieldPathSegmentedFromChangeEvent(event); - this.operationsBuilder.add( - pathCombiner.getPath(segmentedPath2), - arrayValue[segmentedPath2], false); + const segmentedPath = this.getFieldPathSegmentedFromChangeEvent(event); + if (isNotEmpty(arrayValue)) { + this.operationsBuilder.add( + pathCombiner.getPath(segmentedPath), + arrayValue[segmentedPath], + false + ); + } else if (previousValue.isPathEqual(this.formBuilder.getPath(event.model))) { + this.operationsBuilder.remove(pathCombiner.getPath(segmentedPath)); + } + } } From d6dbbd1f1fa853e06d387668491462f939b7fad8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 18 May 2021 10:41:15 +0200 Subject: [PATCH 05/84] Add tests for handleArrayGroupPatch method --- .../section-form-operations.service.spec.ts | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/src/app/submission/sections/form/section-form-operations.service.spec.ts b/src/app/submission/sections/form/section-form-operations.service.spec.ts index c76a15abcb2..9c9b6d971bb 100644 --- a/src/app/submission/sections/form/section-form-operations.service.spec.ts +++ b/src/app/submission/sections/form/section-form-operations.service.spec.ts @@ -4,7 +4,8 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DYNAMIC_FORM_CONTROL_TYPE_GROUP, - DynamicFormControlEvent + DynamicFormControlEvent, + DynamicInputModel } from '@ng-dynamic-forms/core'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; @@ -28,6 +29,7 @@ import { } from '../../../shared/mocks/form-models.mock'; import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model'; import { VocabularyEntry } from '../../../core/submission/vocabularies/models/vocabulary-entry.model'; +import { DynamicRowArrayModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model'; describe('SectionFormOperationsService test suite', () => { let formBuilderService: any; @@ -83,6 +85,11 @@ describe('SectionFormOperationsService test suite', () => { formBuilderService = TestBed.inject(FormBuilderService); }); + afterEach(() => { + jsonPatchOpBuilder.add.calls.reset(); + jsonPatchOpBuilder.remove.calls.reset(); + }); + describe('dispatchOperationsFromEvent', () => { it('should call dispatchOperationsFromRemoveEvent on remove event', () => { const previousValue = new FormFieldPreviousValueObject(['path', 'test'], 'value'); @@ -760,4 +767,87 @@ describe('SectionFormOperationsService test suite', () => { }); }); + describe('handleArrayGroupPatch', () => { + let arrayModel; + let previousValue; + beforeEach(() => { + arrayModel = new DynamicRowArrayModel( + { + id: 'testFormRowArray', + initialCount: 5, + notRepeatable: false, + relationshipConfig: undefined, + submissionId: '1234', + isDraggable: true, + showButtons: false, + groupFactory: () => { + return [ + new DynamicInputModel({ id: 'testFormRowArrayGroupInput' }) + ]; + }, + required: false, + metadataKey: 'dc.contributor.author', + metadataFields: ['dc.contributor.author'], + hasSelectableMetadata: true + } + ); + spyOn(serviceAsAny, 'getFieldPathSegmentedFromChangeEvent').and.returnValue('path'); + previousValue = new FormFieldPreviousValueObject(['path'], null); + }); + + it('should not dispatch a json-path operation when a array value is empty', () => { + formBuilderService.getValueFromModel.and.returnValue({}); + spyOn(previousValue, 'isPathEqual').and.returnValue(false); + + serviceAsAny.handleArrayGroupPatch( + pathCombiner, + dynamicFormControlChangeEvent, + arrayModel, + previousValue + ); + + expect(jsonPatchOpBuilder.add).not.toHaveBeenCalled(); + expect(jsonPatchOpBuilder.remove).not.toHaveBeenCalled(); + }); + + it('should dispatch a json-path add operation when a array value is not empty', () => { + const pathValue = [ + new FormFieldMetadataValueObject('test'), + new FormFieldMetadataValueObject('test two') + ]; + formBuilderService.getValueFromModel.and.returnValue({ + path:pathValue + }); + spyOn(previousValue, 'isPathEqual').and.returnValue(false); + + serviceAsAny.handleArrayGroupPatch( + pathCombiner, + dynamicFormControlChangeEvent, + arrayModel, + previousValue + ); + + expect(jsonPatchOpBuilder.add).toHaveBeenCalledWith( + pathCombiner.getPath('path'), + pathValue, + false + ); + expect(jsonPatchOpBuilder.remove).not.toHaveBeenCalled(); + }); + + it('should dispatch a json-path remove operation when a array value is empty and has previous value', () => { + formBuilderService.getValueFromModel.and.returnValue({}); + spyOn(previousValue, 'isPathEqual').and.returnValue(true); + + serviceAsAny.handleArrayGroupPatch( + pathCombiner, + dynamicFormControlChangeEvent, + arrayModel, + previousValue + ); + + expect(jsonPatchOpBuilder.add).not.toHaveBeenCalled(); + expect(jsonPatchOpBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath('path')); + }); + }); }); From 98dde58f9d0f330945f6faf4665c703709475ad9 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 18 May 2021 11:52:03 +0200 Subject: [PATCH 06/84] [D4CRIS-1080] Fix issue where a replace patch operation was dispatched instead of an add one when field's previous value is empty --- .../section-form-operations.service.spec.ts | 29 ++++++++++++++++++- .../form/section-form-operations.service.ts | 4 +-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/app/submission/sections/form/section-form-operations.service.spec.ts b/src/app/submission/sections/form/section-form-operations.service.spec.ts index 9c9b6d971bb..ec179b6151e 100644 --- a/src/app/submission/sections/form/section-form-operations.service.spec.ts +++ b/src/app/submission/sections/form/section-form-operations.service.spec.ts @@ -574,7 +574,7 @@ describe('SectionFormOperationsService test suite', () => { }); it('should dispatch a json-path remove operation when has a stored value', () => { - const previousValue = new FormFieldPreviousValueObject(['path', 'test'], 'value'); + let previousValue = new FormFieldPreviousValueObject(['path', 'test'], 'value'); const event = Object.assign({}, dynamicFormControlChangeEvent, { model: { parent: mockRowGroupModel @@ -597,6 +597,7 @@ describe('SectionFormOperationsService test suite', () => { spyIndex.and.returnValue(1); spyPath.and.returnValue('path/1'); + previousValue = new FormFieldPreviousValueObject(['path', 'test'], 'value'); serviceAsAny.dispatchOperationsFromChangeEvent(pathCombiner, event, previousValue, true); expect(jsonPatchOpBuilder.remove).toHaveBeenCalledWith(pathCombiner.getPath('path/1')); @@ -627,6 +628,32 @@ describe('SectionFormOperationsService test suite', () => { new FormFieldMetadataValueObject('test')); }); + it('should dispatch a json-path add operation when has a stored value but previous value is empty', () => { + const previousValue = new FormFieldPreviousValueObject(['path', 'test'], null); + const event = Object.assign({}, dynamicFormControlChangeEvent, { + model: { + parent: mockRowGroupModel + } + }); + spyOn(service, 'getFieldPathFromEvent').and.returnValue('path/0'); + spyOn(service, 'getFieldPathSegmentedFromChangeEvent').and.returnValue('path'); + spyOn(service, 'getFieldValueFromChangeEvent').and.returnValue(new FormFieldMetadataValueObject('test')); + spyOn(service, 'getArrayIndexFromEvent').and.returnValue(0); + spyOn(serviceAsAny, 'getValueMap'); + spyOn(serviceAsAny, 'dispatchOperationsFromMap'); + formBuilderService.isQualdropGroup.and.returnValue(false); + formBuilderService.isRelationGroup.and.returnValue(false); + formBuilderService.hasArrayGroupValue.and.returnValue(false); + spyOn(previousValue, 'isPathEqual').and.returnValue(false); + + serviceAsAny.dispatchOperationsFromChangeEvent(pathCombiner, event, previousValue, true); + + expect(jsonPatchOpBuilder.add).toHaveBeenCalledWith( + pathCombiner.getPath('path'), + new FormFieldMetadataValueObject('test'), + true); + }); + it('should dispatch a json-path add operation when has a value and field index is zero or undefined', () => { const previousValue = new FormFieldPreviousValueObject(['path', 'test'], 'value'); const event = Object.assign({}, dynamicFormControlChangeEvent, { diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 8aef798cbca..7174d5da670 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -389,7 +389,7 @@ export class SectionFormOperationsService { this.operationsBuilder.add( pathCombiner.getPath(segmentedPath), value, true); - } else if (previousValue.isPathEqual(this.formBuilder.getPath(event.model)) || hasStoredValue) { + } else if (previousValue.isPathEqual(this.formBuilder.getPath(event.model)) || (hasStoredValue && isNotEmpty(previousValue.value)) ) { // Here model has a previous value changed or stored in the server if (hasValue(event.$event) && hasValue(event.$event.previousIndex)) { if (event.$event.previousIndex < 0) { @@ -422,7 +422,7 @@ export class SectionFormOperationsService { previousValue.delete(); } else if (value.hasValue()) { // Here model has no previous value but a new one - if (isUndefined(this.getArrayIndexFromEvent(event)) || this.getArrayIndexFromEvent(event) === 0) { + if (isUndefined(this.getArrayIndexFromEvent(event)) || this.getArrayIndexFromEvent(event) === 0) { // Model is single field or is part of an array model but is the first item, // so dispatch an add operation that initialize the values of a specific metadata this.operationsBuilder.add( From 3c0cb33bc707a641131c57deb8a2b4f17f32d777 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 19 May 2021 18:16:31 +0200 Subject: [PATCH 07/84] fix failed build --- .../sections/form/section-form-operations.service.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/submission/sections/form/section-form-operations.service.spec.ts b/src/app/submission/sections/form/section-form-operations.service.spec.ts index ec179b6151e..d5798b82c87 100644 --- a/src/app/submission/sections/form/section-form-operations.service.spec.ts +++ b/src/app/submission/sections/form/section-form-operations.service.spec.ts @@ -806,7 +806,6 @@ describe('SectionFormOperationsService test suite', () => { relationshipConfig: undefined, submissionId: '1234', isDraggable: true, - showButtons: false, groupFactory: () => { return [ new DynamicInputModel({ id: 'testFormRowArrayGroupInput' }) From 55affdebced2046e45988e727254bfd662e94842 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 09:28:02 +0200 Subject: [PATCH 08/84] 79597: Add alt text to ds-thumbnail --- src/app/thumbnail/thumbnail.component.html | 2 +- src/app/thumbnail/thumbnail.component.scss | 28 ++++++++++++++++++++++ src/app/thumbnail/thumbnail.component.ts | 6 ++++- src/assets/i18n/en.json5 | 4 ++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index dbf8f6732cf..ec11ba6c0f3 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,4 +1,4 @@
- +
diff --git a/src/app/thumbnail/thumbnail.component.scss b/src/app/thumbnail/thumbnail.component.scss index e2718bac063..9feac243db9 100644 --- a/src/app/thumbnail/thumbnail.component.scss +++ b/src/app/thumbnail/thumbnail.component.scss @@ -1,3 +1,31 @@ img { max-width: 100%; } + +.outer { // .outer/.inner generated ~ https://ratiobuddy.com/ + position: relative; + &:before { + display: block; + content: ""; + width: 100%; + padding-top: (210 / 297) * 100%; // A4 ratio + } + > .inner { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + > .thumbnail-placeholder { + background: var(--bs-gray-100); + border: var(--bs-gray-200) 1px; + color: var(--bs-gray-600); + font-weight: bold; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + } + } +} diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 7e981d5fe65..8d5e780f27d 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -18,7 +18,6 @@ export const THUMBNAIL_PLACEHOLDER = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20 templateUrl: './thumbnail.component.html' }) export class ThumbnailComponent implements OnInit { - /** * The thumbnail Bitstream */ @@ -34,6 +33,11 @@ export class ThumbnailComponent implements OnInit { */ src: string; + /** + * i18n key of thumbnail alt text + */ + @Input() alt? = 'thumbnail.default.alt'; + /** * Initialize the thumbnail. * Use a default image if no actual image is available. diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4c3317a0c0c..ac37eba016c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3539,6 +3539,10 @@ "submission.workflow.tasks.pool.show-detail": "Show detail", + "thumbnail.default.alt": "Thumbnail Image", + + "thumbnail.default.placeholder": "No Thumbnail Available", + "title": "DSpace", From d80da3bbfe2cd6185e9f90538425897336475651 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 09:28:46 +0200 Subject: [PATCH 09/84] 79597: Add HTML placeholder for missing thumbnails --- src/app/thumbnail/thumbnail.component.html | 9 ++++++++- src/app/thumbnail/thumbnail.component.scss | 2 +- src/app/thumbnail/thumbnail.component.ts | 23 ++++++++++++---------- src/assets/i18n/en.json5 | 14 +++++++++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index ec11ba6c0f3..4789917f1c3 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,4 +1,11 @@
- + +
+
+
+ {{ placeholder | translate }} +
+
+
diff --git a/src/app/thumbnail/thumbnail.component.scss b/src/app/thumbnail/thumbnail.component.scss index 9feac243db9..4c8c7d90adb 100644 --- a/src/app/thumbnail/thumbnail.component.scss +++ b/src/app/thumbnail/thumbnail.component.scss @@ -19,7 +19,7 @@ img { > .thumbnail-placeholder { background: var(--bs-gray-100); - border: var(--bs-gray-200) 1px; + border: 1px solid var(--bs-gray-200); color: var(--bs-gray-600); font-weight: bold; display: flex; diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 8d5e780f27d..21a5da35675 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -2,11 +2,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { Bitstream } from '../core/shared/bitstream.model'; import { hasValue } from '../shared/empty.util'; -/** - * A fallback placeholder image as a base64 string - */ -export const THUMBNAIL_PLACEHOLDER = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2293%22%20height%3D%22120%22%20viewBox%3D%220%200%2093%20120%22%20preserveAspectRatio%3D%22none%22%3E%3C!--%0ASource%20URL%3A%20holder.js%2F93x120%3Ftext%3DNo%20Thumbnail%0ACreated%20with%20Holder.js%202.8.2.%0ALearn%20more%20at%20http%3A%2F%2Fholderjs.com%0A(c)%202012-2015%20Ivan%20Malopinsky%20-%20http%3A%2F%2Fimsky.co%0A--%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%3C!%5BCDATA%5B%23holder_1543e460b05%20text%20%7B%20fill%3A%23AAAAAA%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A10pt%20%7D%20%5D%5D%3E%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_1543e460b05%22%3E%3Crect%20width%3D%2293%22%20height%3D%22120%22%20fill%3D%22%23FFFFFF%22%2F%3E%3Cg%3E%3Ctext%20x%3D%2235.6171875%22%20y%3D%2257%22%3ENo%3C%2Ftext%3E%3Ctext%20x%3D%2210.8125%22%20y%3D%2272%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'; - /** * This component renders a given Bitstream as a thumbnail. * One input parameter of type Bitstream is expected. @@ -24,9 +19,10 @@ export class ThumbnailComponent implements OnInit { @Input() thumbnail: Bitstream; /** - * The default image, used if the thumbnail isn't set or can't be downloaded + * The default image, used if the thumbnail isn't set or can't be downloaded. + * If defaultImage is null, a HTML placeholder is used instead. */ - @Input() defaultImage? = THUMBNAIL_PLACEHOLDER; + @Input() defaultImage? = null; /** * The src attribute used in the template to render the image. @@ -38,12 +34,19 @@ export class ThumbnailComponent implements OnInit { */ @Input() alt? = 'thumbnail.default.alt'; + /** + * i18n key of HTML placeholder text + */ + @Input() placeholder? = 'thumbnail.default.placeholder'; + /** * Initialize the thumbnail. * Use a default image if no actual image is available. */ ngOnInit(): void { - if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links) && hasValue(this.thumbnail._links.content) && this.thumbnail._links.content.href) { + if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links) + && hasValue(this.thumbnail._links.content) + && this.thumbnail._links.content.href) { this.src = this.thumbnail._links.content.href; } else { this.src = this.defaultImage; @@ -53,13 +56,13 @@ export class ThumbnailComponent implements OnInit { /** * Handle image download errors. * If the image can't be found, use the defaultImage instead. - * If that also can't be found, use the base64 placeholder. + * If that also can't be found, use null to fall back to the HTML placeholder. */ errorHandler() { if (this.src !== this.defaultImage) { this.src = this.defaultImage; } else { - this.src = THUMBNAIL_PLACEHOLDER; + this.src = null; } } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ac37eba016c..2235eda34d5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3539,10 +3539,24 @@ "submission.workflow.tasks.pool.show-detail": "Show detail", + "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.placeholder": "No Thumbnail Available", + "thumbnail.project.alt": "Project Logo", + + "thumbnail.project.placeholder": "Project Placeholder Image", + + "thumbnail.orgunit.alt": "OrgUnit Logo", + + "thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image", + + "thumbnail.person.alt": "Profile Picture", + + "thumbnail.person.placeholder": "No Profile Picture Available", + + "title": "DSpace", From 7a69a23f0c50e28801d04045e905e80f5929ebd1 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 09:46:54 +0200 Subject: [PATCH 10/84] 79597: Improve placeholder contrast --- src/app/thumbnail/thumbnail.component.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.scss b/src/app/thumbnail/thumbnail.component.scss index 4c8c7d90adb..290b3952123 100644 --- a/src/app/thumbnail/thumbnail.component.scss +++ b/src/app/thumbnail/thumbnail.component.scss @@ -19,8 +19,8 @@ img { > .thumbnail-placeholder { background: var(--bs-gray-100); - border: 1px solid var(--bs-gray-200); - color: var(--bs-gray-600); + border: 1px solid var(--bs-gray-300); + color: var(--bs-gray-800); font-weight: bold; display: flex; justify-content: center; From 4b6e02f773b27897fe0ce178a516d2a12a969bed Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 09:55:19 +0200 Subject: [PATCH 11/84] 79597: Specify i18n text for person, project & orgunit --- .../item-pages/org-unit/org-unit.component.html | 7 ++++++- .../item-pages/person/person.component.html | 6 +++++- .../item-pages/project/project.component.html | 7 ++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 822d4858ce6..14d56d41043 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -9,7 +9,12 @@

- + +
- + +
- + + From c9ff89a143d178f24728c353ede4cb8e21e12d41 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 10:03:18 +0200 Subject: [PATCH 12/84] 79597: Thumbnail placeholder style ~ CSS variables --- src/app/thumbnail/thumbnail.component.scss | 6 +++--- src/styles/_custom_variables.scss | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.scss b/src/app/thumbnail/thumbnail.component.scss index 290b3952123..e2374c96dbd 100644 --- a/src/app/thumbnail/thumbnail.component.scss +++ b/src/app/thumbnail/thumbnail.component.scss @@ -18,9 +18,9 @@ img { left: 0; > .thumbnail-placeholder { - background: var(--bs-gray-100); - border: 1px solid var(--bs-gray-300); - color: var(--bs-gray-800); + background: var(--ds-thumbnail-placeholder-background); + border: var(--ds-thumbnail-placeholder-border); + color: var(--ds-thumbnail-placeholder-color); font-weight: bold; display: flex; justify-content: center; diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index 298be09f677..d0e15642815 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -46,6 +46,9 @@ --ds-edit-item-language-field-width: 43px; --ds-thumbnail-max-width: 175px; + --ds-thumbnail-placeholder-background: #{$gray-100}; + --ds-thumbnail-placeholder-border: 1px solid #{$gray-300}; + --ds-thumbnail-placeholder-color: #{lighten($gray-800, 7%)}; --ds-dso-selector-list-max-height: 475px; --ds-dso-selector-current-background-color: #eeeeee; From 4567f8cc2cfb880e414ead44c7f76326b546517b Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 10:20:46 +0200 Subject: [PATCH 13/84] 79597: Limit thumbnail width & set to portrait --- src/app/thumbnail/thumbnail.component.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/thumbnail/thumbnail.component.scss b/src/app/thumbnail/thumbnail.component.scss index e2374c96dbd..a96147468c5 100644 --- a/src/app/thumbnail/thumbnail.component.scss +++ b/src/app/thumbnail/thumbnail.component.scss @@ -1,3 +1,7 @@ +.thumbnail { + max-width: var(--ds-thumbnail-max-width); +} + img { max-width: 100%; } @@ -8,7 +12,7 @@ img { display: block; content: ""; width: 100%; - padding-top: (210 / 297) * 100%; // A4 ratio + padding-top: (297 / 210) * 100%; // A4 ratio } > .inner { position: absolute; From 363d1d74dff0d82291cef6893f6989ea20f7cda8 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 11:03:51 +0200 Subject: [PATCH 14/84] 79597: Update unit tests --- .../shared/mocks/translate.service.mock.ts | 1 + src/app/thumbnail/thumbnail.component.html | 6 +-- src/app/thumbnail/thumbnail.component.spec.ts | 42 ++++++++++++++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/app/shared/mocks/translate.service.mock.ts b/src/app/shared/mocks/translate.service.mock.ts index 0bc172b408d..38b088e50f3 100644 --- a/src/app/shared/mocks/translate.service.mock.ts +++ b/src/app/shared/mocks/translate.service.mock.ts @@ -3,6 +3,7 @@ import { TranslateService } from '@ngx-translate/core'; export function getMockTranslateService(): TranslateService { return jasmine.createSpyObj('translateService', { get: jasmine.createSpy('get'), + use: jasmine.createSpy('use'), instant: jasmine.createSpy('instant'), setDefaultLang: jasmine.createSpy('setDefaultLang') }); diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index 4789917f1c3..cef88e0192f 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,10 +1,8 @@
- +
-
- {{ placeholder | translate }} -
+
{{ placeholder | translate }}
diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index 21678c9162b..687282a373d 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -1,10 +1,18 @@ -import { DebugElement } from '@angular/core'; +import { DebugElement, Pipe, PipeTransform } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Bitstream } from '../core/shared/bitstream.model'; import { SafeUrlPipe } from '../shared/utils/safe-url-pipe'; -import { THUMBNAIL_PLACEHOLDER, ThumbnailComponent } from './thumbnail.component'; +import { ThumbnailComponent } from './thumbnail.component'; +import { TranslateService } from '@ngx-translate/core'; + +@Pipe({ name: 'translate' }) +class MockTranslatePipe implements PipeTransform { + transform(key: string): string { + return 'TRANSLATED ' + key; + } +} describe('ThumbnailComponent', () => { let comp: ThumbnailComponent; @@ -14,7 +22,7 @@ describe('ThumbnailComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ThumbnailComponent, SafeUrlPipe] + declarations: [ThumbnailComponent, SafeUrlPipe, MockTranslatePipe], }).compileComponents(); })); @@ -39,6 +47,19 @@ describe('ThumbnailComponent', () => { const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href); }); + it('should include the alt text', () => { + const thumbnail = new Bitstream(); + thumbnail._links = { + self: { href: 'self.url' }, + bundle: { href: 'bundle.url' }, + format: { href: 'format.url' }, + content: { href: 'content.url' }, + }; + comp.thumbnail = thumbnail; + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); + }); }); describe(`when the thumbnail doesn't exist`, () => { describe('and there is a default image', () => { @@ -48,13 +69,24 @@ describe('ThumbnailComponent', () => { comp.errorHandler(); expect(comp.src).toBe(comp.defaultImage); }); + it('should include the alt text', () => { + comp.src = 'http://bit.stream'; + comp.defaultImage = 'http://default.img'; + comp.errorHandler(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); + }); }); describe('and there is no default image', () => { it('should display the placeholder', () => { comp.src = 'http://default.img'; - comp.defaultImage = 'http://default.img'; comp.errorHandler(); - expect(comp.src).toBe(THUMBNAIL_PLACEHOLDER); + expect(comp.src).toBe(null); + + fixture.detectChanges(); + const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement; + expect(placeholder.innerHTML).toBe('TRANSLATED ' + comp.placeholder); }); }); }); From ca7d45ff0c0fa2b221bb27587a7ce67e5905cb7d Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 25 May 2021 11:18:08 +0200 Subject: [PATCH 15/84] 79597: Fix tslint issue --- src/app/thumbnail/thumbnail.component.spec.ts | 2 +- src/app/thumbnail/thumbnail.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index 687282a373d..82eadc04def 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -5,8 +5,8 @@ import { Bitstream } from '../core/shared/bitstream.model'; import { SafeUrlPipe } from '../shared/utils/safe-url-pipe'; import { ThumbnailComponent } from './thumbnail.component'; -import { TranslateService } from '@ngx-translate/core'; +// tslint:disable-next-line:pipe-prefix @Pipe({ name: 'translate' }) class MockTranslatePipe implements PipeTransform { transform(key: string): string { diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 21a5da35675..11dee370379 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -10,7 +10,7 @@ import { hasValue } from '../shared/empty.util'; @Component({ selector: 'ds-thumbnail', styleUrls: ['./thumbnail.component.scss'], - templateUrl: './thumbnail.component.html' + templateUrl: './thumbnail.component.html', }) export class ThumbnailComponent implements OnInit { /** From 41c07e74ca5c3ad3a6a69492c40873c8b7fb7089 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 08:59:32 +0200 Subject: [PATCH 16/84] 79597: Add input to toggle thumbnail max-width --- src/app/thumbnail/thumbnail.component.html | 9 +++++---- src/app/thumbnail/thumbnail.component.scss | 2 +- src/app/thumbnail/thumbnail.component.ts | 7 ++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index cef88e0192f..645bce9b809 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,8 +1,9 @@ -
- -
+
+ +
-
{{ placeholder | translate }}
+
{{ placeholder | translate }}
diff --git a/src/app/thumbnail/thumbnail.component.scss b/src/app/thumbnail/thumbnail.component.scss index a96147468c5..b15238afac3 100644 --- a/src/app/thumbnail/thumbnail.component.scss +++ b/src/app/thumbnail/thumbnail.component.scss @@ -1,4 +1,4 @@ -.thumbnail { +.limit-width { max-width: var(--ds-thumbnail-max-width); } diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 11dee370379..30911644f72 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -5,7 +5,7 @@ import { hasValue } from '../shared/empty.util'; /** * This component renders a given Bitstream as a thumbnail. * One input parameter of type Bitstream is expected. - * If no Bitstream is provided, a holderjs image will be rendered instead. + * If no Bitstream is provided, a HTML placeholder will be rendered instead. */ @Component({ selector: 'ds-thumbnail', @@ -39,6 +39,11 @@ export class ThumbnailComponent implements OnInit { */ @Input() placeholder? = 'thumbnail.default.placeholder'; + /** + * Limit thumbnail width to --ds-thumbnail-max-width + */ + @Input() limitWidth? = true; + /** * Initialize the thumbnail. * Use a default image if no actual image is available. From 4f38821bb3ff269d890ebf381beb5f0ff701152a Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 09:10:31 +0200 Subject: [PATCH 17/84] 79597: Replace ds-grid-thumbnail with ds-thumbnail --- ...ournal-issue-search-result-grid-element.component.html | 8 ++++---- ...urnal-volume-search-result-grid-element.component.html | 8 ++++---- .../journal-search-result-grid-element.component.html | 8 ++++---- .../org-unit-search-result-grid-element.component.html | 8 ++++---- .../person-search-result-grid-element.component.html | 8 ++++---- .../project-search-result-grid-element.component.html | 8 ++++---- .../collection-grid-element.component.html | 8 ++++---- .../community-grid-element.component.html | 8 ++++---- src/app/shared/object-grid/object-grid.component.scss | 2 +- .../collection-search-result-grid-element.component.html | 8 ++++---- .../community-search-result-grid-element.component.html | 8 ++++---- .../item/item-search-result-grid-element.component.html | 8 ++++---- 12 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html index df6c9e60c03..feb282d3a7f 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html index cdc19b7f14d..aa2352b284d 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html index bacd657663d..8fdad598277 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html index 2c3087d701c..b8be58a603f 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html index 005fa9cc831..8281eb0e04f 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html index e84e8c49d06..88498a4d677 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html index 9b9d1747040..d47e897edcc 100644 --- a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html +++ b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +

{{object.name}}

diff --git a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html index f676ba303b9..63097c4f579 100644 --- a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html +++ b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +

{{object.name}}

diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss index 46675615f0c..68a7f2f9913 100644 --- a/src/app/shared/object-grid/object-grid.component.scss +++ b/src/app/shared/object-grid/object-grid.component.scss @@ -1,7 +1,7 @@ :host ::ng-deep { --ds-wrapper-grid-spacing: calc(var(--bs-spacer) / 2); - div.thumbnail > img { + div.thumbnail > .thumbnail-content { height: var(--ds-card-thumbnail-height); width: 100%; display: block; diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html index f8c75fc0d4f..739fa6c7a80 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +
diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html index 8025213b3bc..d8c253c8a9c 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html index 85aeb63a6b3..bc168537219 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html @@ -6,14 +6,14 @@
- - + +
- - + +
From 6cbd9dc920c7937e4f5217ee40bd4b2242ecf328 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 09:21:00 +0200 Subject: [PATCH 18/84] 79597: Remove GridThumbnailComponent --- .../grid-thumbnail.component.html | 3 - .../grid-thumbnail.component.scss | 0 .../grid-thumbnail.component.spec.ts | 50 ------------- .../grid-thumbnail.component.ts | 72 ------------------- src/app/shared/shared.module.ts | 5 +- 5 files changed, 1 insertion(+), 129 deletions(-) delete mode 100644 src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html delete mode 100644 src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.scss delete mode 100644 src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts delete mode 100644 src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html deleted file mode 100644 index 1df4026f839..00000000000 --- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.scss b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts deleted file mode 100644 index 825a4d5c60a..00000000000 --- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { DebugElement } from '@angular/core'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { Bitstream } from '../../../core/shared/bitstream.model'; -import { SafeUrlPipe } from '../../utils/safe-url-pipe'; - -import { GridThumbnailComponent } from './grid-thumbnail.component'; - -describe('GridThumbnailComponent', () => { - let comp: GridThumbnailComponent; - let fixture: ComponentFixture; - let de: DebugElement; - let el: HTMLElement; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [GridThumbnailComponent, SafeUrlPipe] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(GridThumbnailComponent); - comp = fixture.componentInstance; // BannerComponent test instance - de = fixture.debugElement.query(By.css('div.thumbnail')); - el = de.nativeElement; - }); - - it('should display image', () => { - const thumbnail = new Bitstream(); - thumbnail._links = { - self: { href: 'self.url' }, - bundle: { href: 'bundle.url' }, - format: { href: 'format.url' }, - content: { href: 'content.url' }, - }; - comp.thumbnail = thumbnail; - fixture.detectChanges(); - const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href); - }); - - it('should display placeholder', () => { - const thumbnail = new Bitstream(); - comp.thumbnail = thumbnail; - fixture.detectChanges(); - const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('src')).toBe(comp.defaultImage); - }); - -}); diff --git a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts b/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts deleted file mode 100644 index 92d93686dc2..00000000000 --- a/src/app/shared/object-grid/grid-thumbnail/grid-thumbnail.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - Component, - Input, - OnChanges, - OnInit, - SimpleChanges, -} from '@angular/core'; -import { Bitstream } from '../../../core/shared/bitstream.model'; -import { hasValue } from '../../empty.util'; - -/** - * This component renders a given Bitstream as a thumbnail. - * One input parameter of type Bitstream is expected. - * If no Bitstream is provided, a holderjs image will be rendered instead. - */ - -@Component({ - selector: 'ds-grid-thumbnail', - styleUrls: ['./grid-thumbnail.component.scss'], - templateUrl: './grid-thumbnail.component.html', -}) -export class GridThumbnailComponent implements OnInit, OnChanges { - @Input() thumbnail: Bitstream; - - data: any = {}; - - /** - * The default 'holder.js' image - */ - @Input() defaultImage? = - 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjYwIiBoZWlnaHQ9IjE4MCIgdmlld0JveD0iMCAwIDI2MCAxODAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjwhLS0KU291cmNlIFVSTDogaG9sZGVyLmpzLzEwMCV4MTgwL3RleHQ6Tm8gVGh1bWJuYWlsCkNyZWF0ZWQgd2l0aCBIb2xkZXIuanMgMi42LjAuCkxlYXJuIG1vcmUgYXQgaHR0cDovL2hvbGRlcmpzLmNvbQooYykgMjAxMi0yMDE1IEl2YW4gTWFsb3BpbnNreSAtIGh0dHA6Ly9pbXNreS5jbwotLT48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWyNob2xkZXJfMTVmNzJmMmFlMGIgdGV4dCB7IGZpbGw6I0FBQUFBQTtmb250LXdlaWdodDpib2xkO2ZvbnQtZmFtaWx5OkFyaWFsLCBIZWx2ZXRpY2EsIE9wZW4gU2Fucywgc2Fucy1zZXJpZiwgbW9ub3NwYWNlO2ZvbnQtc2l6ZToxM3B0IH0gXV0+PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImhvbGRlcl8xNWY3MmYyYWUwYiI+PHJlY3Qgd2lkdGg9IjI2MCIgaGVpZ2h0PSIxODAiIGZpbGw9IiNFRUVFRUUiLz48Zz48dGV4dCB4PSI3Mi4yNDIxODc1IiB5PSI5NiI+Tm8gVGh1bWJuYWlsPC90ZXh0PjwvZz48L2c+PC9zdmc+'; - - src: string; - - errorHandler(event) { - event.currentTarget.src = this.defaultImage; - } - - /** - * Initialize the src - */ - ngOnInit(): void { - this.src = this.defaultImage; - - this.checkThumbnail(this.thumbnail); - } - - /** - * If the old input is undefined and the new one is a bitsream then set src - */ - ngOnChanges(changes: SimpleChanges): void { - if ( - !hasValue(changes.thumbnail.previousValue) && - hasValue(changes.thumbnail.currentValue) - ) { - this.checkThumbnail(changes.thumbnail.currentValue); - } - } - - /** - * check if the Bitstream has any content than set the src - */ - checkThumbnail(thumbnail: Bitstream) { - if ( - hasValue(thumbnail) && - hasValue(thumbnail._links) && - thumbnail._links.content.href - ) { - this.src = thumbnail._links.content.href; - } - } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 4de0f2901ef..c5a91bd02cc 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -46,7 +46,6 @@ import { ThumbnailComponent } from '../thumbnail/thumbnail.component'; import { SearchFormComponent } from './search-form/search-form.component'; import { SearchResultGridElementComponent } from './object-grid/search-result-grid-element/search-result-grid-element.component'; import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component'; -import { GridThumbnailComponent } from './object-grid/grid-thumbnail/grid-thumbnail.component'; import { VarDirective } from './utils/var.directive'; import { AuthNavMenuComponent } from './auth-nav-menu/auth-nav-menu.component'; import { LogOutComponent } from './log-out/log-out.component'; @@ -54,8 +53,7 @@ import { FormComponent } from './form/form.component'; import { DsDynamicOneboxComponent } from './form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component'; import { DsDynamicScrollableDropdownComponent } from './form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component'; import { - DsDynamicFormControlContainerComponent, - dsDynamicFormControlMapFn + DsDynamicFormControlContainerComponent, dsDynamicFormControlMapFn, } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component'; import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form.component'; import { DragClickDirective } from './utils/drag-click.directive'; @@ -340,7 +338,6 @@ const COMPONENTS = [ SidebarFilterComponent, SidebarFilterSelectedOptionComponent, ThumbnailComponent, - GridThumbnailComponent, UploaderComponent, FileDropzoneNoUploaderComponent, ItemListPreviewComponent, From 9b95fc5de9b07adc038332f0c67848bf13448129 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 11:18:40 +0200 Subject: [PATCH 19/84] 79597: Add space between rows in FullFileSectionComponent --- .../file-section/full-file-section.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html index bc1c63cc322..c5393055dfc 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html @@ -11,7 +11,7 @@
{{"item.page.filesection.original.bundle" [retainScrollPosition]="true"> -
+
From c717fc5ec815d0b29b244781b1efcf2e5b0ac0b7 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 15:51:54 +0200 Subject: [PATCH 20/84] 79597: Fix thumbnails ~ item page direct request --- .../metadata-field-wrapper.component.html | 2 +- .../publication/publication.component.html | 2 +- .../item-types/shared/item.component.ts | 21 ++++++---- .../untyped-item/untyped-item.component.html | 2 +- .../journal-issue.component.html | 2 +- .../journal-volume.component.html | 2 +- .../item-pages/journal/journal.component.html | 2 +- ...-search-result-grid-element.component.html | 2 +- .../org-unit/org-unit.component.html | 2 +- .../item-pages/person/person.component.html | 2 +- .../item-pages/project/project.component.html | 2 +- src/app/thumbnail/thumbnail.component.html | 18 ++++---- src/app/thumbnail/thumbnail.component.ts | 41 ++++++++++++++----- 13 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html index c791cec600a..a41aa0d67af 100644 --- a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html +++ b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html @@ -1,4 +1,4 @@ -
+
{{ label }}
diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.html b/src/app/+item-page/simple/item-types/publication/publication.component.html index 73219cbb8f4..0758f7cda47 100644 --- a/src/app/+item-page/simple/item-types/publication/publication.component.html +++ b/src/app/+item-page/simple/item-types/publication/publication.component.html @@ -10,7 +10,7 @@

- + 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 120eda930f7..8763d8c899e 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 @@ -4,8 +4,10 @@ import { environment } from '../../../../../environments/environment'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Item } from '../../../../core/shared/item.model'; -import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { getFirstSucceededRemoteDataPayload, takeUntilCompletedRemoteData } from '../../../../core/shared/operators'; import { getItemPageRoute } from '../../../item-page-routing-paths'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { RemoteData } from '../../../../core/data/remote-data'; @Component({ selector: 'ds-item', @@ -17,6 +19,11 @@ import { getItemPageRoute } from '../../../item-page-routing-paths'; export class ItemComponent implements OnInit { @Input() object: Item; + /** + * The Item's thumbnail + */ + thumbnail$: BehaviorSubject>; + /** * Route to the item page */ @@ -28,12 +35,12 @@ export class ItemComponent implements OnInit { ngOnInit(): void { this.itemPageRoute = getItemPageRoute(this.object); - } - // TODO refactor to return RemoteData, and thumbnail template to deal with loading - getThumbnail(): Observable { - return this.bitstreamDataService.getThumbnailFor(this.object).pipe( - getFirstSucceededRemoteDataPayload() - ); + this.thumbnail$ = new BehaviorSubject>(undefined); + this.bitstreamDataService.getThumbnailFor(this.object).pipe( + takeUntilCompletedRemoteData(), + ).subscribe((rd: RemoteData) => { + this.thumbnail$.next(rd); + }); } } diff --git a/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html index 8d46a2c5a9f..3e20da30944 100644 --- a/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -10,7 +10,7 @@

- + diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html index 5749c5797d6..af87daa2434 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html @@ -9,7 +9,7 @@

- +
- +
- +
- +
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 14d56d41043..67b79163ed8 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -9,7 +9,7 @@

-
- diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/app/entity-groups/research-entities/item-pages/project/project.component.html index 0590c8bfe4b..facecb49967 100644 --- a/src/app/entity-groups/research-entities/item-pages/project/project.component.html +++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.html @@ -10,7 +10,7 @@

diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index 645bce9b809..593c1c10b82 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,10 +1,14 @@
- -
-
-
{{ placeholder | translate }}
+ + text-content + + + +
+
+
{{ placeholder | translate }}
+
-
+
- diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 30911644f72..ec1e7eb368f 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -1,6 +1,8 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnChanges } from '@angular/core'; import { Bitstream } from '../core/shared/bitstream.model'; import { hasValue } from '../shared/empty.util'; +import { RemoteData } from '../core/data/remote-data'; +import { BITSTREAM } from '../core/shared/bitstream.resource-type'; /** * This component renders a given Bitstream as a thumbnail. @@ -12,11 +14,11 @@ import { hasValue } from '../shared/empty.util'; styleUrls: ['./thumbnail.component.scss'], templateUrl: './thumbnail.component.html', }) -export class ThumbnailComponent implements OnInit { +export class ThumbnailComponent implements OnChanges { /** * The thumbnail Bitstream */ - @Input() thumbnail: Bitstream; + @Input() thumbnail: Bitstream | RemoteData; /** * The default image, used if the thumbnail isn't set or can't be downloaded. @@ -27,7 +29,7 @@ export class ThumbnailComponent implements OnInit { /** * The src attribute used in the template to render the image. */ - src: string; + src: string = null; /** * i18n key of thumbnail alt text @@ -44,18 +46,37 @@ export class ThumbnailComponent implements OnInit { */ @Input() limitWidth? = true; + isLoading: boolean; + /** - * Initialize the thumbnail. + * Resolve the thumbnail. * Use a default image if no actual image is available. */ - ngOnInit(): void { - if (hasValue(this.thumbnail) && hasValue(this.thumbnail._links) - && hasValue(this.thumbnail._links.content) - && this.thumbnail._links.content.href) { - this.src = this.thumbnail._links.content.href; + ngOnChanges(): void { + if (this.thumbnail === undefined || this.thumbnail === null) { + return; + } + if (this.thumbnail instanceof Bitstream) { + this.resolveThumbnail(this.thumbnail as Bitstream) + } else { + const thumbnailRD = this.thumbnail as RemoteData; + if (thumbnailRD.isLoading) { + this.isLoading = true; + } else { + this.resolveThumbnail(thumbnailRD.payload as Bitstream); + } + } + } + + private resolveThumbnail(thumbnail: Bitstream): void { + if (hasValue(thumbnail) && hasValue(thumbnail._links) + && hasValue(thumbnail._links.content) + && thumbnail._links.content.href) { + this.src = thumbnail._links.content.href; } else { this.src = this.defaultImage; } + this.isLoading = false; } /** From bcfb890e1aa1dae27e87fd57afc1cd6232532f0e Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 16:34:52 +0200 Subject: [PATCH 21/84] 79597: Update unit tests --- src/app/thumbnail/thumbnail.component.html | 2 +- src/app/thumbnail/thumbnail.component.spec.ts | 125 +++++++++++++----- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index 593c1c10b82..a7f4c51510d 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,4 +1,4 @@ -
+
text-content diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index 82eadc04def..bc9d1597501 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -5,6 +5,10 @@ import { Bitstream } from '../core/shared/bitstream.model'; import { SafeUrlPipe } from '../shared/utils/safe-url-pipe'; import { ThumbnailComponent } from './thumbnail.component'; +import { RemoteData } from '../core/data/remote-data'; +import { + createFailedRemoteDataObject, createPendingRemoteDataObject, createSuccessfulRemoteDataObject, +} from '../shared/remote-data.utils'; // tslint:disable-next-line:pipe-prefix @Pipe({ name: 'translate' }) @@ -28,40 +32,12 @@ describe('ThumbnailComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ThumbnailComponent); - comp = fixture.componentInstance; // BannerComponent test instance + comp = fixture.componentInstance; // ThumbnailComponent test instance de = fixture.debugElement.query(By.css('div.thumbnail')); el = de.nativeElement; }); - describe('when the thumbnail exists', () => { - it('should display an image', () => { - const thumbnail = new Bitstream(); - thumbnail._links = { - self: { href: 'self.url' }, - bundle: { href: 'bundle.url' }, - format: { href: 'format.url' }, - content: { href: 'content.url' }, - }; - comp.thumbnail = thumbnail; - fixture.detectChanges(); - const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href); - }); - it('should include the alt text', () => { - const thumbnail = new Bitstream(); - thumbnail._links = { - self: { href: 'self.url' }, - bundle: { href: 'bundle.url' }, - format: { href: 'format.url' }, - content: { href: 'content.url' }, - }; - comp.thumbnail = thumbnail; - fixture.detectChanges(); - const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); - }); - }); - describe(`when the thumbnail doesn't exist`, () => { + const withoutThumbnail = () => { describe('and there is a default image', () => { it('should display the default image', () => { comp.src = 'http://bit.stream'; @@ -73,6 +49,7 @@ describe('ThumbnailComponent', () => { comp.src = 'http://bit.stream'; comp.defaultImage = 'http://default.img'; comp.errorHandler(); + comp.ngOnChanges(); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); @@ -84,10 +61,98 @@ describe('ThumbnailComponent', () => { comp.errorHandler(); expect(comp.src).toBe(null); + comp.ngOnChanges(); fixture.detectChanges(); const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement; expect(placeholder.innerHTML).toBe('TRANSLATED ' + comp.placeholder); }); }); + }; + + describe('with thumbnail as Bitstream', () => { + let thumbnail: Bitstream; + beforeEach(() => { + thumbnail = new Bitstream(); + thumbnail._links = { + self: { href: 'self.url' }, + bundle: { href: 'bundle.url' }, + format: { href: 'format.url' }, + content: { href: 'content.url' }, + }; + }); + + it('should display an image', () => { + comp.thumbnail = thumbnail; + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href); + }); + + it('should include the alt text', () => { + comp.thumbnail = thumbnail; + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); + }); + + describe('when there is no thumbnail', () => { + withoutThumbnail(); + }); + }); + + describe('with thumbnail as RemoteData', () => { + let thumbnail: RemoteData; + + describe('while loading', () => { + beforeEach(() => { + thumbnail = createPendingRemoteDataObject(); + }); + + it('should show a loading animation', () => { + comp.thumbnail = thumbnail; + comp.ngOnChanges(); + fixture.detectChanges(); + expect(de.query(By.css('ds-loading'))).toBeTruthy(); + }); + }); + + describe('when there is a thumbnail', () => { + beforeEach(() => { + const bitstream = new Bitstream(); + bitstream._links = { + self: { href: 'self.url' }, + bundle: { href: 'bundle.url' }, + format: { href: 'format.url' }, + content: { href: 'content.url' }, + }; + thumbnail = createSuccessfulRemoteDataObject(bitstream); + }); + + it('should display an image', () => { + comp.thumbnail = thumbnail; + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('src')).toBe(comp.thumbnail.payload._links.content.href); + }); + + it('should display the alt text', () => { + comp.thumbnail = thumbnail; + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); + }); + }); + + describe('when there is no thumbnail', () => { + beforeEach(() => { + thumbnail = createFailedRemoteDataObject(); + }); + + withoutThumbnail(); + }); }); }); From 120ecc6988cec452ab5c897155b712c368ed88df Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 17:09:57 +0200 Subject: [PATCH 22/84] 79597: Fix ds-metadata-field-wrapper for thumbnails --- .../metadata-field-wrapper.component.html | 2 +- .../metadata-field-wrapper.component.ts | 7 +------ .../item-types/publication/publication.component.html | 2 +- .../item-types/untyped-item/untyped-item.component.html | 2 +- .../item-pages/journal-issue/journal-issue.component.html | 2 +- .../journal-volume/journal-volume.component.html | 2 +- .../item-pages/journal/journal.component.html | 2 +- .../item-pages/org-unit/org-unit.component.html | 2 +- .../item-pages/person/person.component.html | 2 +- .../item-pages/project/project.component.html | 2 +- .../item-detail-preview/item-detail-preview.component.html | 2 +- src/app/thumbnail/thumbnail.component.html | 2 +- src/app/thumbnail/thumbnail.component.ts | 3 +-- 13 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html index a41aa0d67af..d69f87883bb 100644 --- a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html +++ b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html @@ -1,4 +1,4 @@ -
+
{{ label }}
diff --git a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts index 8af108cceb8..8c4e200423b 100644 --- a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts +++ b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts @@ -17,10 +17,5 @@ export class MetadataFieldWrapperComponent { */ @Input() label: string; - /** - * Make hasNoValue() available in the template - */ - hasNoValue(o: any): boolean { - return hasNoValue(o); - } + @Input() hideIfNoTextContent = true; } diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.html b/src/app/+item-page/simple/item-types/publication/publication.component.html index 0758f7cda47..57b460c8144 100644 --- a/src/app/+item-page/simple/item-types/publication/publication.component.html +++ b/src/app/+item-page/simple/item-types/publication/publication.component.html @@ -9,7 +9,7 @@

- + diff --git a/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html index 3e20da30944..7ae9a1a9097 100644 --- a/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -9,7 +9,7 @@

- + diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html index af87daa2434..8e357140d86 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html @@ -8,7 +8,7 @@

- +
- +
- +
- +
- +
- +

- + diff --git a/src/app/thumbnail/thumbnail.component.html b/src/app/thumbnail/thumbnail.component.html index a7f4c51510d..bf70928392f 100644 --- a/src/app/thumbnail/thumbnail.component.html +++ b/src/app/thumbnail/thumbnail.component.html @@ -1,4 +1,4 @@ -
+
text-content diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index ec1e7eb368f..3e122cde786 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -2,7 +2,6 @@ import { Component, Input, OnChanges } from '@angular/core'; import { Bitstream } from '../core/shared/bitstream.model'; import { hasValue } from '../shared/empty.util'; import { RemoteData } from '../core/data/remote-data'; -import { BITSTREAM } from '../core/shared/bitstream.resource-type'; /** * This component renders a given Bitstream as a thumbnail. @@ -57,7 +56,7 @@ export class ThumbnailComponent implements OnChanges { return; } if (this.thumbnail instanceof Bitstream) { - this.resolveThumbnail(this.thumbnail as Bitstream) + this.resolveThumbnail(this.thumbnail as Bitstream); } else { const thumbnailRD = this.thumbnail as RemoteData; if (thumbnailRD.isLoading) { From 4b238e18423f97583e3454e3a0947395e6fcdc18 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 27 May 2021 18:03:43 +0200 Subject: [PATCH 23/84] 79597: Update ds-metadata-field-wrapper unit tests --- .../metadata-field-wrapper.component.spec.ts | 109 +++++++++++------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts index 9c62b80cad7..e17e5397b7e 100644 --- a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts +++ b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.component'; @@ -6,35 +6,34 @@ import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.componen /* tslint:disable:max-classes-per-file */ @Component({ selector: 'ds-component-without-content', - template: '\n' + + template: '\n' + '' }) -class NoContentComponent {} +class NoContentComponent { + public hideIfNoTextContent = true; +} @Component({ selector: 'ds-component-with-empty-spans', - template: '\n' + + template: '\n' + ' \n' + ' \n' + '' }) -class SpanContentComponent {} +class SpanContentComponent { + @Input() hideIfNoTextContent = true; +} @Component({ selector: 'ds-component-with-text', - template: '\n' + + template: '\n' + ' The quick brown fox jumps over the lazy dog\n' + '' }) -class TextContentComponent {} +class TextContentComponent { + @Input() hideIfNoTextContent = true; +} -@Component({ - selector: 'ds-component-with-image', - template: '\n' + - ' an alt text\n' + - '' -}) -class ImgContentComponent {} /* tslint:enable:max-classes-per-file */ describe('MetadataFieldWrapperComponent', () => { @@ -43,7 +42,7 @@ describe('MetadataFieldWrapperComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [MetadataFieldWrapperComponent, NoContentComponent, SpanContentComponent, TextContentComponent, ImgContentComponent] + declarations: [MetadataFieldWrapperComponent, NoContentComponent, SpanContentComponent, TextContentComponent] }).compileComponents(); })); @@ -58,38 +57,60 @@ describe('MetadataFieldWrapperComponent', () => { expect(component).toBeDefined(); }); - it('should not show the component when there is no content', () => { - const parentFixture = TestBed.createComponent(NoContentComponent); - parentFixture.detectChanges(); - const parentNative = parentFixture.nativeElement; - const nativeWrapper = parentNative.querySelector(wrapperSelector); - expect(nativeWrapper.classList.contains('d-none')).toBe(true); - }); + describe('with hideIfNoTextContent=true', () => { + it('should not show the component when there is no content', () => { + const parentFixture = TestBed.createComponent(NoContentComponent); + parentFixture.detectChanges(); + const parentNative = parentFixture.nativeElement; + const nativeWrapper = parentNative.querySelector(wrapperSelector); + expect(nativeWrapper.classList.contains('d-none')).toBe(true); + }); - it('should not show the component when there is DOM content but not text or an image', () => { - const parentFixture = TestBed.createComponent(SpanContentComponent); - parentFixture.detectChanges(); - const parentNative = parentFixture.nativeElement; - const nativeWrapper = parentNative.querySelector(wrapperSelector); - expect(nativeWrapper.classList.contains('d-none')).toBe(true); - }); + it('should not show the component when there is no text content', () => { + const parentFixture = TestBed.createComponent(SpanContentComponent); + parentFixture.detectChanges(); + const parentNative = parentFixture.nativeElement; + const nativeWrapper = parentNative.querySelector(wrapperSelector); + expect(nativeWrapper.classList.contains('d-none')).toBe(true); + }); - it('should show the component when there is text content', () => { - const parentFixture = TestBed.createComponent(TextContentComponent); - parentFixture.detectChanges(); - const parentNative = parentFixture.nativeElement; - const nativeWrapper = parentNative.querySelector(wrapperSelector); - parentFixture.detectChanges(); - expect(nativeWrapper.classList.contains('d-none')).toBe(false); + it('should show the component when there is text content', () => { + const parentFixture = TestBed.createComponent(TextContentComponent); + parentFixture.detectChanges(); + const parentNative = parentFixture.nativeElement; + const nativeWrapper = parentNative.querySelector(wrapperSelector); + parentFixture.detectChanges(); + expect(nativeWrapper.classList.contains('d-none')).toBe(false); + }); }); - it('should show the component when there is img content', () => { - const parentFixture = TestBed.createComponent(ImgContentComponent); - parentFixture.detectChanges(); - const parentNative = parentFixture.nativeElement; - const nativeWrapper = parentNative.querySelector(wrapperSelector); - parentFixture.detectChanges(); - expect(nativeWrapper.classList.contains('d-none')).toBe(false); - }); + describe('with hideIfNoTextContent=false', () => { + it('should show the component when there is no content', () => { + const parentFixture = TestBed.createComponent(NoContentComponent); + parentFixture.componentInstance.hideIfNoTextContent = false; + parentFixture.detectChanges(); + const parentNative = parentFixture.nativeElement; + const nativeWrapper = parentNative.querySelector(wrapperSelector); + expect(nativeWrapper.classList.contains('d-none')).toBe(false); + }); + it('should show the component when there is no text content', () => { + const parentFixture = TestBed.createComponent(SpanContentComponent); + parentFixture.componentInstance.hideIfNoTextContent = false; + parentFixture.detectChanges(); + const parentNative = parentFixture.nativeElement; + const nativeWrapper = parentNative.querySelector(wrapperSelector); + expect(nativeWrapper.classList.contains('d-none')).toBe(false); + }); + + it('should show the component when there is text content', () => { + const parentFixture = TestBed.createComponent(TextContentComponent); + parentFixture.componentInstance.hideIfNoTextContent = false; + parentFixture.detectChanges(); + const parentNative = parentFixture.nativeElement; + const nativeWrapper = parentNative.querySelector(wrapperSelector); + parentFixture.detectChanges(); + expect(nativeWrapper.classList.contains('d-none')).toBe(false); + }); + }); }); From 95b98d3f7960776645eff3da8df00a0349a32a94 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Mon, 31 May 2021 10:20:28 +0200 Subject: [PATCH 24/84] 79597: Remove unused imports --- .../metadata-field-wrapper/metadata-field-wrapper.component.ts | 1 - src/app/+item-page/simple/item-types/shared/item.component.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts index 8c4e200423b..5c6b99248f7 100644 --- a/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts +++ b/src/app/+item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts @@ -1,5 +1,4 @@ import { Component, Input } from '@angular/core'; -import { hasNoValue } from '../../../shared/empty.util'; /** * This component renders any content inside this wrapper. 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 8763d8c899e..130f67edc71 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 @@ -1,10 +1,9 @@ import { Component, Input, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; import { environment } from '../../../../../environments/environment'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Item } from '../../../../core/shared/item.model'; -import { getFirstSucceededRemoteDataPayload, takeUntilCompletedRemoteData } from '../../../../core/shared/operators'; +import { takeUntilCompletedRemoteData } from '../../../../core/shared/operators'; import { getItemPageRoute } from '../../../item-page-routing-paths'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { RemoteData } from '../../../../core/data/remote-data'; From 2dfed863edcb84d7b58066a97daf48e0457d4252 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 28 May 2021 18:49:47 +0200 Subject: [PATCH 25/84] [DSC-75] Fix issue while deleting multiple qualdrop value --- src/app/shared/form/form.component.ts | 9 ++++++++- .../sections/form/section-form-operations.service.ts | 9 +++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 42469ddba23..8d75d7f13a6 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -309,9 +309,16 @@ export class FormComponent implements OnDestroy, OnInit { removeItem($event, arrayContext: DynamicFormArrayModel, index: number): void { const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray; const event = this.getEvent($event, arrayContext, index, 'remove'); + if (this.formBuilderService.isQualdropGroup(event.model as DynamicFormControlModel)) { + // In case of qualdrop value remove event must be dispatched before removing the control from array + this.removeArrayItem.emit(event); + } this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext); this.formService.changeForm(this.formId, this.formModel); - this.removeArrayItem.emit(event); + if (!this.formBuilderService.isQualdropGroup(event.model as DynamicFormControlModel)) { + // dispatch remove event for any field type except for qualdrop value + this.removeArrayItem.emit(event); + } } insertItem($event, arrayContext: DynamicFormArrayModel, index: number): void { diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 7174d5da670..adba46bf3ab 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -298,17 +298,14 @@ export class SectionFormOperationsService { event: DynamicFormControlEvent, previousValue: FormFieldPreviousValueObject): void { - if (event.context && event.context instanceof DynamicFormArrayGroupModel) { - // Model is a DynamicRowArrayModel - this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context, previousValue); - return; - } - const path = this.getFieldPathFromEvent(event); const value = this.getFieldValueFromChangeEvent(event); console.log(value); if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) { this.dispatchOperationsFromMap(this.getQualdropValueMap(event), pathCombiner, event, previousValue); + } else if (event.context && event.context instanceof DynamicFormArrayGroupModel) { + // Model is a DynamicRowArrayModel + this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context, previousValue); } else if ((isNotEmpty(value) && typeof value === 'string') || (isNotEmpty(value) && value instanceof FormFieldMetadataValueObject && value.hasValue())) { this.operationsBuilder.remove(pathCombiner.getPath(path)); } From 3e53b7c7b17f83547f727833f4408a479330f6e0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 1 Jun 2021 14:09:17 +0200 Subject: [PATCH 26/84] [CST-4248] Add possibility to add additional content to form.component --- src/app/shared/form/form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index 39ccda360f7..de24880b3b7 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -48,7 +48,7 @@ - +
From c150fb881eae270591e3b4329b595d4aa6a7d2d2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 1 Jun 2021 14:12:12 +0200 Subject: [PATCH 27/84] [CST-4248] bitstream authorizations page --- .../bitstream-authorizations.component.html | 10 +++ ...bitstream-authorizations.component.spec.ts | 84 +++++++++++++++++++ .../bitstream-authorizations.component.ts | 40 +++++++++ .../bitstream-page-routing.module.ts | 36 ++++++++ .../+bitstream-page/bitstream-page.module.ts | 2 + src/assets/i18n/en.json5 | 8 +- 6 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.html create mode 100644 src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.spec.ts create mode 100644 src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts diff --git a/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.html b/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.html new file mode 100644 index 00000000000..804bb4f8910 --- /dev/null +++ b/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.html @@ -0,0 +1,10 @@ + diff --git a/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.spec.ts b/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.spec.ts new file mode 100644 index 00000000000..c41351f3806 --- /dev/null +++ b/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.spec.ts @@ -0,0 +1,84 @@ +import { CommonModule } from '@angular/common'; +import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; + +import { cold } from 'jasmine-marbles'; +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; + +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { BitstreamAuthorizationsComponent } from './bitstream-authorizations.component'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; + +describe('BitstreamAuthorizationsComponent', () => { + let comp: BitstreamAuthorizationsComponent; + let fixture: ComponentFixture>; + + const bitstream = Object.assign(new Bitstream(), { + sizeBytes: 10000, + metadata: { + 'dc.title': [ + { + value: 'file name', + language: null + } + ] + }, + _links: { + content: { href: 'file-selflink' } + } + }); + + const bitstreamRD = createSuccessfulRemoteDataObject(bitstream); + + const routeStub = { + data: observableOf({ + bitstream: bitstreamRD + }) + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + declarations: [BitstreamAuthorizationsComponent], + providers: [ + { provide: ActivatedRoute, useValue: routeStub }, + ChangeDetectorRef, + BitstreamAuthorizationsComponent, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamAuthorizationsComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + comp = null; + fixture.destroy(); + }); + + it('should create', () => { + expect(comp).toBeTruthy(); + }); + + it('should init dso remote data properly', (done) => { + const expected = cold('(a|)', { a: bitstreamRD }); + expect(comp.dsoRD$).toBeObservable(expected); + done(); + }); +}); diff --git a/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts b/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts new file mode 100644 index 00000000000..adc06387804 --- /dev/null +++ b/src/app/+bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Observable } from 'rxjs'; +import { first, map } from 'rxjs/operators'; + +import { RemoteData } from '../../core/data/remote-data'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-collection-authorizations', + templateUrl: './bitstream-authorizations.component.html', +}) +/** + * Component that handles the Collection Authorizations + */ +export class BitstreamAuthorizationsComponent implements OnInit { + + /** + * The initial DSO object + */ + public dsoRD$: Observable>; + + /** + * Initialize instance variables + * + * @param {ActivatedRoute} route + */ + constructor( + private route: ActivatedRoute + ) { + } + + /** + * Initialize the component, setting up the collection + */ + ngOnInit(): void { + this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.bitstream)); + } +} diff --git a/src/app/+bitstream-page/bitstream-page-routing.module.ts b/src/app/+bitstream-page/bitstream-page-routing.module.ts index bbbd65f279d..284f29f7b41 100644 --- a/src/app/+bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/+bitstream-page/bitstream-page-routing.module.ts @@ -4,8 +4,14 @@ import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; import { BitstreamDownloadPageComponent } from '../shared/bitstream-download-page/bitstream-download-page.component'; +import { ResourcePolicyTargetResolver } from '../shared/resource-policies/resolvers/resource-policy-target.resolver'; +import { ResourcePolicyCreateComponent } from '../shared/resource-policies/create/resource-policy-create.component'; +import { ResourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver'; +import { ResourcePolicyEditComponent } from '../shared/resource-policies/edit/resource-policy-edit.component'; +import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component'; const EDIT_BITSTREAM_PATH = ':id/edit'; +const EDIT_BITSTREAM_AUTHORIZATIONS_PATH = ':id/authorizations'; /** * Routing module to help navigate Bitstream pages @@ -27,6 +33,36 @@ const EDIT_BITSTREAM_PATH = ':id/edit'; bitstream: BitstreamPageResolver }, canActivate: [AuthenticatedGuard] + }, + { + path: EDIT_BITSTREAM_AUTHORIZATIONS_PATH, + + children: [ + { + path: 'create', + resolve: { + resourcePolicyTarget: ResourcePolicyTargetResolver + }, + component: ResourcePolicyCreateComponent, + data: { title: 'resource-policies.create.page.title', showBreadcrumbs: true } + }, + { + path: 'edit', + resolve: { + resourcePolicy: ResourcePolicyResolver + }, + component: ResourcePolicyEditComponent, + data: { title: 'resource-policies.edit.page.title', showBreadcrumbs: true } + }, + { + path: '', + resolve: { + bitstream: BitstreamPageResolver + }, + component: BitstreamAuthorizationsComponent, + data: { title: 'bitstream.edit.authorizations.title', showBreadcrumbs: true } + } + ] } ]) ], diff --git a/src/app/+bitstream-page/bitstream-page.module.ts b/src/app/+bitstream-page/bitstream-page.module.ts index 24b4cd512ff..80e5ad14e39 100644 --- a/src/app/+bitstream-page/bitstream-page.module.ts +++ b/src/app/+bitstream-page/bitstream-page.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '../shared/shared.module'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { BitstreamPageRoutingModule } from './bitstream-page-routing.module'; +import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component'; /** * This module handles all components that are necessary for Bitstream related pages @@ -14,6 +15,7 @@ import { BitstreamPageRoutingModule } from './bitstream-page-routing.module'; BitstreamPageRoutingModule ], declarations: [ + BitstreamAuthorizationsComponent, EditBitstreamPageComponent ] }) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4c3317a0c0c..44725337be1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -530,6 +530,12 @@ + "bitstream.edit.authorizations.link": "Edit bitstream's Policies", + + "bitstream.edit.authorizations.title": "Edit bitstream's Policies", + + "bitstream.edit.return": "Back", + "bitstream.edit.bitstream": "Bitstream: ", "bitstream.edit.form.description.hint": "Optionally, provide a brief description of the file, for example \"Main article\" or \"Experiment data readings\".", @@ -1817,8 +1823,6 @@ "item.page.description": "Description", - "item.page.edit": "Edit this item", - "item.page.journal-issn": "Journal ISSN", "item.page.journal-title": "Journal Title", From eaaad88443694d30d4ff4eed5cbba929c1c389e8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 1 Jun 2021 14:13:19 +0200 Subject: [PATCH 28/84] [CST-4248] Remove embargo form field and add link to bitstream authorization page --- .../edit-bitstream-page.component.html | 6 +++- .../edit-bitstream-page.component.spec.ts | 21 ++++---------- .../edit-bitstream-page.component.ts | 28 +++---------------- 3 files changed, 15 insertions(+), 40 deletions(-) diff --git a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html index fd13e249a04..cbb587cca48 100644 --- a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html +++ b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html @@ -19,7 +19,11 @@

{{bitstreamRD?.payload?.name}} ({{bitstreamRD?.payl [submitLabel]="'form.save'" (submitForm)="onSubmit()" (cancel)="onCancel()" - (dfChange)="onChange($event)"> + (dfChange)="onChange($event)"> + +

diff --git a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts index 2e7eb4e1d1d..9c2cb3a0932 100644 --- a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts +++ b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts @@ -18,12 +18,8 @@ import { hasValue } from '../../shared/empty.util'; import { FormControl, FormGroup } from '@angular/forms'; import { FileSizePipe } from '../../shared/utils/file-size-pipe'; import { VarDirective } from '../../shared/utils/var.directive'; -import { - createSuccessfulRemoteDataObject, - createSuccessfulRemoteDataObject$ -} from '../../shared/remote-data.utils'; -import { RouterStub } from '../../shared/testing/router.stub'; -import { getEntityEditRoute, getItemEditRoute } from '../../+item-page/item-page-routing-paths'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { getEntityEditRoute } from '../../+item-page/item-page-routing-paths'; import { createPaginatedList } from '../../shared/testing/utils.test'; import { Item } from '../../core/shared/item.model'; @@ -39,7 +35,6 @@ let bitstream: Bitstream; let selectedFormat: BitstreamFormat; let allFormats: BitstreamFormat[]; let router: Router; -let routerStub; describe('EditBitstreamPageComponent', () => { let comp: EditBitstreamPageComponent; @@ -129,10 +124,6 @@ describe('EditBitstreamPageComponent', () => { findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) }); - const itemPageUrl = `fake-url/some-uuid`; - routerStub = Object.assign(new RouterStub(), { - url: `${itemPageUrl}` - }); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule], declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], @@ -142,7 +133,6 @@ describe('EditBitstreamPageComponent', () => { { provide: ActivatedRoute, useValue: { data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }), snapshot: { queryParams: {} } } }, { provide: BitstreamDataService, useValue: bitstreamService }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, - { provide: Router, useValue: routerStub }, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -154,7 +144,8 @@ describe('EditBitstreamPageComponent', () => { fixture = TestBed.createComponent(EditBitstreamPageComponent); comp = fixture.componentInstance; fixture.detectChanges(); - router = (comp as any).router; + router = TestBed.inject(Router); + spyOn(router, 'navigate'); }); describe('on startup', () => { @@ -241,14 +232,14 @@ describe('EditBitstreamPageComponent', () => { it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => { comp.itemId = 'some-uuid1'; comp.navigateToItemEditBitstreams(); - expect(routerStub.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']); + expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']); }); }); describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => { it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => { comp.itemId = undefined; comp.navigateToItemEditBitstreams(); - expect(routerStub.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']); + expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']); }); }); }); diff --git a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts index 8a4d5846477..4ad0aac7ef5 100644 --- a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts +++ b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts @@ -19,10 +19,10 @@ import { cloneDeep } from 'lodash'; import { BitstreamDataService } from '../../core/data/bitstream-data.service'; import { getAllSucceededRemoteDataPayload, - getFirstSucceededRemoteDataPayload, - getRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData + getFirstSucceededRemoteDataPayload, + getRemoteDataPayload } from '../../core/shared/operators'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service'; @@ -131,15 +131,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { rows: 10 }); - /** - * The Dynamic Input Model for the file's embargo (disabled on this page) - */ - embargoModel = new DynamicInputModel({ - id: 'embargo', - name: 'embargo', - disabled: true - }); - /** * The Dynamic Input Model for the selected format */ @@ -159,7 +150,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { /** * All input models in a simple array for easier iterations */ - inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.embargoModel, this.selectedFormatModel, this.newFormatModel]; + inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel, this.newFormatModel]; /** * The dynamic form fields used for editing the information of a bitstream @@ -179,12 +170,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { this.descriptionModel ] }), - new DynamicFormGroupModel({ - id: 'embargoContainer', - group: [ - this.embargoModel - ] - }), new DynamicFormGroupModel({ id: 'formatContainer', group: [ @@ -243,11 +228,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { host: 'row' } }, - embargoContainer: { - grid: { - host: 'row' - } - }, formatContainer: { grid: { host: 'row' From 4683df431cd88494b423a5c4a58397f01eeb6827 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Mon, 31 May 2021 14:37:29 +0200 Subject: [PATCH 29/84] 79730: Exclude search facet link from tablist --- .../search-facet-option/search-facet-option.component.html | 1 + .../search-facet-selected-option.component.html | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index cf4876e34fe..055a2d25001 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -1,4 +1,5 @@ diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html index 4bcfc029668..411dc766d05 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html @@ -1,4 +1,5 @@ From ffb320373debd251ef146e2450a9fa7a2cd3f9d8 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Mon, 31 May 2021 14:41:36 +0200 Subject: [PATCH 30/84] 79730: Add labels around facet checkbox inputs --- .../search-facet-option.component.html | 8 +++++--- .../search-facet-selected-option.component.html | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 055a2d25001..e2e57e73706 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -2,10 +2,12 @@ [tabIndex]="-1" [routerLink]="[searchLink]" [queryParams]="addQueryParams" queryParamsHandling="merge"> - - + + {{filterValue.count}} diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html index 411dc766d05..d6cb7a3d797 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html @@ -2,8 +2,10 @@ [tabIndex]="-1" [routerLink]="[searchLink]" [queryParams]="removeQueryParams" queryParamsHandling="merge"> + From 5b490203b2453477a52d90132a71ea61576dd7ac Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 1 Jun 2021 09:29:01 +0200 Subject: [PATCH 31/84] 79730: Add labels around FilterInputSuggestionsComponent inputs --- .../org-unit-input-suggestions.component.html | 2 +- .../filter-input-suggestions.component.html | 25 ++++++++---- .../input-suggestions.component.ts | 9 ++++- .../search-authority-filter.component.html | 3 +- .../search-hierarchy-filter.component.html | 3 +- .../search-text-filter.component.html | 3 +- src/assets/i18n/en.json5 | 40 +++++++++++++++++++ 7 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.html index e177b2b561e..87a422e7db4 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.html +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.html @@ -21,4 +21,4 @@
- \ No newline at end of file + diff --git a/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html b/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html index 7a9481f2f14..f1b0ba90237 100644 --- a/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html +++ b/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html @@ -3,13 +3,22 @@ (keydown.arrowdown)="shiftFocusDown($event)" (keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()" (dsClickOutside)="close();"> - - + + + + + - \ No newline at end of file + diff --git a/src/app/shared/input-suggestions/input-suggestions.component.ts b/src/app/shared/input-suggestions/input-suggestions.component.ts index c48dcfb8318..7e05dbcc8c6 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.ts +++ b/src/app/shared/input-suggestions/input-suggestions.component.ts @@ -53,6 +53,11 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange */ @Input() valid = true; + /** + * Label for the input field. Used for screen readers. + */ + @Input() label? = ''; + /** * Output for when the form is submitted */ @@ -106,10 +111,10 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange @Input() disabled = false; propagateChange = (_: any) => { /* Empty implementation */ - } + }; propagateTouch = (_: any) => { /* Empty implementation */ - } + }; /** * When any of the inputs change, check if we should still show the suggestions diff --git a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index 5e6bcfaf8b9..4a7f769f211 100644 --- a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -16,7 +16,8 @@

Date: Tue, 1 Jun 2021 09:45:17 +0200 Subject: [PATCH 32/84] 79730: Add labels around date range inputs --- .../search-range-filter.component.html | 31 +++++++++++++------ src/assets/i18n/en.json5 | 8 +++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html index 8c4fe2b1743..e4e8152e973 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html @@ -3,18 +3,31 @@
- +
- +
- +
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e9f22fd52ce..90aac07a544 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2914,9 +2914,13 @@ "search.filters.filter.dateIssued.head": "Date", - "search.filters.filter.dateIssued.max.placeholder": "Minimum Date", + "search.filters.filter.dateIssued.max.placeholder": "Maximum Date", - "search.filters.filter.dateIssued.min.placeholder": "Maximum Date", + "search.filters.filter.dateIssued.max.label": "End", + + "search.filters.filter.dateIssued.min.placeholder": "Minimum Date", + + "search.filters.filter.dateIssued.min.label": "Start", "search.filters.filter.dateSubmitted.head": "Date submitted", From abe26ce9f828daf5a63e18cb79b2270dfe430cbe Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 1 Jun 2021 12:42:40 +0200 Subject: [PATCH 33/84] 79730: Keyboard navigation for expandable filter facets --- .../search-filter.component.html | 12 +++--- .../search-filter.component.scss | 38 ++++++++++++++++--- .../search-filter/search-filter.component.ts | 24 ++++++++++++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-filter.component.html index eb2105f4e75..b71111de6ac 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.html @@ -1,17 +1,19 @@ -
-
+
+
+
+ class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }"> diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.scss b/src/app/shared/search/search-filters/search-filter/search-filter.component.scss index 518e7c9d5f9..7e2631b55f7 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.scss +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.scss @@ -1,10 +1,36 @@ :host .facet-filter { - border: 1px solid var(--bs-light); - cursor: pointer; - .search-filter-wrapper.closed { - overflow: hidden; + border: 1px solid var(--bs-light); + cursor: pointer; + line-height: 0; + + .search-filter-wrapper { + line-height: var(--bs-line-height-base); + &.closed { + overflow: hidden; } - .filter-toggle { - line-height: var(--bs-line-height-base); + &.notab { + visibility: hidden; } + } + + .filter-toggle { + line-height: var(--bs-line-height-base); + text-align: right; + position: relative; + top: -0.125rem; // Fix weird outline shape in Chrome + } + + > button { + appearance: none; + border: 0; + padding: 0; + background: transparent; + width: 100%; + outline: none !important; + } + + &.focus { + outline: none; + box-shadow: var(--bs-input-btn-focus-box-shadow); + } } diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 31ace10a7d9..23cd92a6014 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -37,6 +37,16 @@ export class SearchFilterComponent implements OnInit { */ closed: boolean; + /** + * True when the filter controls should be hidden & removed from the tablist + */ + notab: boolean; + + /** + * True when the filter toggle button is focused + */ + focusBox: boolean = false; + /** * Emits true when the filter is currently collapsed in the store */ @@ -112,6 +122,9 @@ export class SearchFilterComponent implements OnInit { if (event.fromState === 'collapsed') { this.closed = false; } + if (event.toState === 'collapsed') { + this.notab = true; + } } /** @@ -122,6 +135,17 @@ export class SearchFilterComponent implements OnInit { if (event.toState === 'collapsed') { this.closed = true; } + if (event.fromState === 'collapsed') { + this.notab = false; + } + } + + get regionId(): string { + return `search-filter-region-${this.constructor['ɵcmp'].id}`; + } + + get toggleId(): string { + return `search-filter-toggle-${this.constructor['ɵcmp'].id}`; } /** From cb3f5ad259a5088b2bf70659a00559d67ec8ff58 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 1 Jun 2021 13:04:49 +0200 Subject: [PATCH 34/84] 79730: Add null href to more/collapse toggle links --- .../search-authority-filter.component.html | 10 ++++++---- .../search-boolean-filter.component.html | 10 ++++++---- .../search-hierarchy-filter.component.html | 10 ++++++---- .../search-text-filter.component.html | 10 ++++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index 4a7f769f211..44aed494e3d 100644 --- a/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -8,11 +8,13 @@
{{"search.filters.filter.show-more" - | translate}} + (click)="showMore()" href="javascript:void(0);"> + {{"search.filters.filter.show-more" | translate}} + {{"search.filters.filter.show-less" - | translate}} + (click)="showFirstPageOnly()" href="javascript:void(0);"> + {{"search.filters.filter.show-less" | translate}} +
{{"search.filters.filter.show-more" - | translate}} + (click)="showMore()" href="javascript:void(0);"> + {{"search.filters.filter.show-more" | translate}} + {{"search.filters.filter.show-less" - | translate}} + (click)="showFirstPageOnly()" href="javascript:void(0);"> + {{"search.filters.filter.show-less" | translate}} +
diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index 2154ae2e24b..49ca6fe3fdd 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -8,11 +8,13 @@
{{"search.filters.filter.show-more" - | translate}} + (click)="showMore()" href="javascript:void(0);"> + {{"search.filters.filter.show-more" | translate}} + {{"search.filters.filter.show-less" - | translate}} + (click)="showFirstPageOnly()" href="javascript:void(0);"> + {{"search.filters.filter.show-less" | translate}} +
{{"search.filters.filter.show-more" - | translate}} + (click)="showMore()" href="javascript:void(0);"> + {{"search.filters.filter.show-more" | translate}} + {{"search.filters.filter.show-less" - | translate}} + (click)="showFirstPageOnly()" href="javascript:void(0);"> + {{"search.filters.filter.show-less" | translate}} +
Date: Tue, 1 Jun 2021 14:01:48 +0200 Subject: [PATCH 35/84] 79730: Improve slider handles keyboard control --- .../search-range-filter/search-range-filter.component.html | 5 +++-- .../search-range-filter/search-range-filter.component.scss | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html index e4e8152e973..3a6a6565c07 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html @@ -32,8 +32,9 @@ - + [dsDebounce]="500" (onDebounce)="onSubmit()" + [(ngModel)]="range" ngDefaultControl> +
diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss index 2c98280e7fb..f26806abfb2 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss @@ -21,6 +21,7 @@ } &:focus { outline: none; + box-shadow: var(--bs-input-btn-focus-box-shadow); } } From c60fa2c441257ebe46f3d3ab5a85334b3b33dc9e Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 1 Jun 2021 15:20:33 +0200 Subject: [PATCH 36/84] 79730: Don't submit date slider changes until keyup --- .../search-range-filter.component.html | 3 ++- .../search-range-filter.component.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html index 3a6a6565c07..0ebd5f74a29 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html @@ -32,7 +32,8 @@ diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 62b1cb98a65..b23a2d82241 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -68,6 +68,12 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple */ sub: Subscription; + /** + * Whether the sider is being controlled by the keyboard. + * Supresses any changes until the key is released. + */ + keyboardControl: boolean; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected router: Router, @@ -104,6 +110,10 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple * Submits new custom range values to the range filter from the widget */ onSubmit() { + if (this.keyboardControl) { + return; // don't submit if a key is being held down + } + const newMin = this.range[0] !== this.min ? [this.range[0]] : null; const newMax = this.range[1] !== this.max ? [this.range[1]] : null; this.router.navigate(this.getSearchLinkParts(), { @@ -117,6 +127,14 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple this.filter = ''; } + startKeyboardControl(): void { + this.keyboardControl = true; + } + + stopKeyboardControl(): void { + this.keyboardControl = false; + } + /** * TODO when upgrading nouislider, verify that this check is still needed. * Prevents AoT bug From 08878941aba73fd5e849b0e4f7c6dede293d7701 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Tue, 1 Jun 2021 15:31:30 +0200 Subject: [PATCH 37/84] 79730: Fix tslint issues --- .../shared/input-suggestions/input-suggestions.component.ts | 4 ++-- .../search-filters/search-filter/search-filter.component.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/shared/input-suggestions/input-suggestions.component.ts b/src/app/shared/input-suggestions/input-suggestions.component.ts index 7e05dbcc8c6..7b5c9f34f2a 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.ts +++ b/src/app/shared/input-suggestions/input-suggestions.component.ts @@ -111,10 +111,10 @@ export class InputSuggestionsComponent implements ControlValueAccessor, OnChange @Input() disabled = false; propagateChange = (_: any) => { /* Empty implementation */ - }; + } propagateTouch = (_: any) => { /* Empty implementation */ - }; + } /** * When any of the inputs change, check if we should still show the suggestions diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 23cd92a6014..57c4f991db3 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -45,7 +45,7 @@ export class SearchFilterComponent implements OnInit { /** * True when the filter toggle button is focused */ - focusBox: boolean = false; + focusBox = false; /** * Emits true when the filter is currently collapsed in the store @@ -141,10 +141,12 @@ export class SearchFilterComponent implements OnInit { } get regionId(): string { + // tslint:disable-next-line:no-string-literal return `search-filter-region-${this.constructor['ɵcmp'].id}`; } get toggleId(): string { + // tslint:disable-next-line:no-string-literal return `search-filter-toggle-${this.constructor['ɵcmp'].id}`; } From d37d043531ab41ab4ed3b99835472788ed12a636 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 3 Jun 2021 15:10:20 +0200 Subject: [PATCH 38/84] 79730: Show input labels when available --- .../filter-input-suggestions.component.html | 17 ++++++++----- .../search-range-filter.component.html | 24 ++++++++++--------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html b/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html index f1b0ba90237..d239c8db8df 100644 --- a/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html +++ b/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html @@ -3,20 +3,25 @@ (keydown.arrowdown)="shiftFocusDown($event)" (keydown.arrowup)="shiftFocusUp($event)" (keydown.esc)="close()" (dsClickOutside)="close();"> - +
+ +
+ [ngModelOptions]="{standalone: true}" autocomplete="off" + />