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 21cce391bbc..97129d94800 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 @@ -64,6 +64,7 @@ import { take, tap, } from 'rxjs/operators'; +import { ALTERNATIVE_CONTENT_TAG } from 'src/app/submission/sections/upload/section-upload-constants'; import { getEntityEditRoute } from '../../item-page/item-page-routing-paths'; import { ErrorComponent } from '../../shared/error/error.component'; @@ -193,6 +194,15 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }, ); + /** + * The Dynamic Switch Model for set as alternative content + */ + alternativeContentModel = new DynamicCustomSwitchModel({ + id: 'alternativeContent', + name: 'alternativeContent', + }, + ); + /** * The Dynamic TextArea Model for the file's description */ @@ -311,7 +321,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { /** * All input models in a simple array for easier iterations */ - inputModels = [this.primaryBitstreamModel, this.fileNameModel, this.descriptionModel, this.selectedFormatModel, + inputModels = [this.primaryBitstreamModel, this.alternativeContentModel, this.fileNameModel, this.descriptionModel, this.selectedFormatModel, this.newFormatModel]; /** @@ -323,6 +333,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { id: 'fileNamePrimaryContainer', group: [ this.primaryBitstreamModel, + this.alternativeContentModel, this.fileNameModel, ], }, { @@ -367,6 +378,14 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { container: 'text-right', }, }, + alternativeContent: { + grid: { + container: 'col-12 mt-2', + }, + element: { + container: 'text-right', + }, + }, description: { grid: { host: 'col-12 d-inline-block', @@ -543,6 +562,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { fileNamePrimaryContainer: { fileName: bitstream.name, primaryBitstream: this.primaryBitstreamUUID === bitstream.uuid, + alternativeContent: bitstream.firstMetadataValue('dspace.bitstream.type') === ALTERNATIVE_CONTENT_TAG, }, descriptionContainer: { description: bitstream.firstMetadataValue('dc.description'), @@ -609,7 +629,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ private updateFieldTranslation(fieldModel) { fieldModel.label = this.translate.instant(this.KEY_PREFIX + fieldModel.id + this.LABEL_KEY_SUFFIX); - if (fieldModel.id !== this.primaryBitstreamModel.id) { + if (fieldModel.id !== this.primaryBitstreamModel.id && fieldModel.id !== this.alternativeContentModel.id) { fieldModel.hint = this.translate.instant(this.KEY_PREFIX + fieldModel.id + this.HINT_KEY_SUFFIX); } } @@ -635,6 +655,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { const isNewFormat = this.selectedFormat.id !== this.originalFormat.id; const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream; const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid; + const isAlternativeContent = updatedValues.fileNamePrimaryContainer.alternativeContent; let bitstream$; let bundle$: Observable; @@ -700,6 +721,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } else { bitstream$ = of(this.bitstream); } + if (isAlternativeContent) { + updatedBitstream.setMetadata('dspace.bitstream.type', undefined, ...[ALTERNATIVE_CONTENT_TAG]); + } else { + updatedBitstream.removeMetadata('dspace.bitstream.type'); + } combineLatest([bundle$, bitstream$]).pipe( tap(([bundle]) => this.bundle = bundle), diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.html b/src/app/item-page/simple/field-components/file-section/file-section.component.html index 79440ac6551..44b2cf24e66 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.html @@ -4,12 +4,15 @@
@for (file of bitstreams; track file; let last = $last) { - - @if (primaryBitstreamId === file.id) { - {{ 'item.page.bitstreams.primary' | translate }} - } - {{ dsoNameService.getName(file) }} - + + @if (primaryBitstreamId === file.id) { + {{ 'item.page.bitstreams.primary' | translate }} + } + @if (file.metadata['dspace.bitstream.type']?.[0]?.value === alternativeContentTag) { + {{ 'item.page.bitstreams.alternativeContent' | translate }} + } + {{ dsoNameService.getName(file) }} + ({{(file?.sizeBytes) | dsFileSize }}) @if (!last) { diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.ts b/src/app/item-page/simple/field-components/file-section/file-section.component.ts index 8f5df0c4de9..6282b9a26a1 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.ts +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.ts @@ -24,6 +24,7 @@ import { TranslateService, } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs'; +import { ALTERNATIVE_CONTENT_TAG } from 'src/app/submission/sections/upload/section-upload-constants'; import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component'; import { ThemedLoadingComponent } from '../../../../shared/loading/themed-loading.component'; @@ -68,6 +69,11 @@ export class FileSectionComponent implements OnInit { primaryBitstreamId: string; + /** + * Alternative content tag variable, this variable is only used to make tag available in HTMl + */ + public alternativeContentTag: string = ALTERNATIVE_CONTENT_TAG; + constructor( protected bitstreamDataService: BitstreamDataService, protected notificationsService: NotificationsService, diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html index f358b53a783..6d665a3ce86 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component.html @@ -1,4 +1,4 @@ -
+
{ comp.formModel = compAsAny.buildFileEditForm(); - const models = [DynamicCustomSwitchModel, DynamicFormGroupModel, DynamicFormArrayModel]; + const models = [DynamicCustomSwitchModel, DynamicCustomSwitchModel, DynamicFormGroupModel, DynamicFormArrayModel]; expect(comp.formModel).toBeDefined(); expect(comp.formModel.length).toBe(models.length); @@ -238,7 +238,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { expect(comp.formModel[i] instanceof model).toBeTruthy(); }); - expect((comp.formModel[2] as DynamicFormArrayModel).groups.length).toBe(2); + expect((comp.formModel[3] as DynamicFormArrayModel).groups.length).toBe(2); const startDateModel = formbuilderService.findById('startDate', comp.formModel); expect(startDateModel.max).toEqual(maxStartDate); const endDateModel = formbuilderService.findById('endDate', comp.formModel); @@ -303,6 +303,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { compAsAny.fileData = fileData; compAsAny.pathCombiner = pathCombiner; compAsAny.isPrimary = null; + compAsAny.isAlternativeContent = null; formService.validateAllFormFields.and.callFake(() => null); formService.isValid.and.returnValue(of(true)); formService.getFormData.and.returnValue(of(mockFileFormData)); 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 85fd7444529..59d4d8a2a97 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 @@ -43,6 +43,7 @@ import { mergeMap, take, } from 'rxjs/operators'; +import { MetadataValue } from 'src/app/core/shared/metadata.models'; import { DynamicCustomSwitchModel } from 'src/app/shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; import { BtnDisabledDirective } from '../../../../../shared/btn-disabled.directive'; @@ -51,7 +52,10 @@ import { FormComponent } from '../../../../../shared/form/form.component'; import { FormService } from '../../../../../shared/form/form.service'; import { SubmissionService } from '../../../../submission.service'; import { SectionUploadService } from '../../section-upload.service'; -import { POLICY_DEFAULT_WITH_LIST } from '../../section-upload-constants'; +import { + ALTERNATIVE_CONTENT_TAG, + POLICY_DEFAULT_WITH_LIST, +} from '../../section-upload-constants'; import { BITSTREAM_ACCESS_CONDITION_GROUP_CONFIG, BITSTREAM_ACCESS_CONDITION_GROUP_LAYOUT, @@ -63,6 +67,8 @@ import { BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT, + BITSTREAM_FORM_ALTERNATIVE_CONTENT, + BITSTREAM_FORM_ALTERNATIVE_CONTENT_LAYOUT, BITSTREAM_FORM_PRIMARY, BITSTREAM_FORM_PRIMARY_LAYOUT, BITSTREAM_METADATA_FORM_GROUP_CONFIG, @@ -97,6 +103,12 @@ implements OnInit, OnDestroy { */ isPrimary: boolean; + /** + * The indicator if is alternative content bitstream + * @type {boolean, null} + */ + isAlternativeContent: boolean; + /** * The list of available access condition * @type {Array} @@ -218,6 +230,9 @@ implements OnInit, OnDestroy { const primaryBitstreamModel: any = this.formBuilderService.findById('primary', formModel, this.fileIndex); primaryBitstreamModel.value = this.isPrimary || false; + const alternativeContentBitstreamModel: any = this.formBuilderService.findById('alternativeContent', formModel, this.fileIndex); + alternativeContentBitstreamModel.value = this.isAlternativeContent || false; + this.fileData.accessConditions.forEach((accessCondition, index) => { Array.of('name', 'startDate', 'endDate') .filter((key) => accessCondition.hasOwnProperty(key) && isNotEmpty(accessCondition[key])) @@ -317,10 +332,13 @@ implements OnInit, OnDestroy { configDescr, ]), }); + configForm.rows = configForm.rows.filter((row: any) => row.fields[0].selectableMetadata[0].metadata !== 'dc.type'); const formModel: DynamicFormControlModel[] = []; formModel.push(new DynamicCustomSwitchModel(BITSTREAM_FORM_PRIMARY, BITSTREAM_FORM_PRIMARY_LAYOUT)); + formModel.push(new DynamicCustomSwitchModel(BITSTREAM_FORM_ALTERNATIVE_CONTENT, BITSTREAM_FORM_ALTERNATIVE_CONTENT_LAYOUT)); + const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG); metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration( this.submissionId, @@ -434,13 +452,29 @@ implements OnInit, OnDestroy { mergeMap((formData: any) => { this.uploadService.updatePrimaryBitstreamOperation(this.pathCombiner.getPath('primary'), this.isPrimary, formData.primary[0], this.fileId); + //Set operations to bitstream alternative content flag + const alternativeContentPath = 'metadata/dspace.bitstream.type'; + if (formData.alternativeContent[0]) { + const metadata = Object.assign(new MetadataValue(), { + language: null, + value: ALTERNATIVE_CONTENT_TAG, + authority: null, + place: 0, + }); + this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, alternativeContentPath]), metadata, true); + } else { + this.operationsBuilder.remove(this.pathCombiner.getPath([...pathFragment, alternativeContentPath])); + } + // collect bitstream metadata Object.keys((formData.metadata)) .filter((key) => isNotEmpty(formData.metadata[key])) .forEach((key) => { const metadataKey = key.replace(/_/g, '.'); - const path = `metadata/${metadataKey}`; - this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, path]), formData.metadata[key], true); + if (metadataKey !== 'dc.type') { + const path = `metadata/${metadataKey}`; + this.operationsBuilder.add(this.pathCombiner.getPath([...pathFragment, path]), formData.metadata[key], true); + } }); Object.keys((this.fileData.metadata)) .filter((key) => isNotEmpty(this.fileData.metadata[key])) @@ -448,8 +482,10 @@ implements OnInit, OnDestroy { .filter((key) => this.formMetadata.includes(key)) .forEach((key) => { const metadataKey = key.replace(/_/g, '.'); - const path = `metadata/${metadataKey}`; - this.operationsBuilder.remove(this.pathCombiner.getPath([...pathFragment, path])); + if (metadataKey !== 'dc.type') { + const path = `metadata/${metadataKey}`; + this.operationsBuilder.remove(this.pathCombiner.getPath([...pathFragment, path])); + } }); const accessConditionsToSave = []; if (formData.hasOwnProperty('accessConditions')) { 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 65d233081ca..896864f756a 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 @@ -70,6 +70,19 @@ export const BITSTREAM_FORM_PRIMARY: DynamicSwitchModelConfig = { label: 'bitstream.edit.form.primaryBitstream.label', }; +export const BITSTREAM_FORM_ALTERNATIVE_CONTENT_LAYOUT: DynamicFormControlLayout = { + element: { + host: 'col-12', + container: 'text-right', + }, +}; + +export const BITSTREAM_FORM_ALTERNATIVE_CONTENT: DynamicSwitchModelConfig = { + id: 'alternativeContent', + name: 'alternativeContent', + label: 'bitstream.edit.form.alternativeContent.label', +}; + export const BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_CONFIG: DynamicDatePickerModelConfig = { id: 'startDate', diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index c1cdf266aaf..e85b3ee2498 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -6,6 +6,7 @@
-
- - - - - - - +
+ + + + +
diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.scss b/src/app/submission/sections/upload/file/section-upload-file.component.scss index e69de29bb2d..305d111948d 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.scss +++ b/src/app/submission/sections/upload/file/section-upload-file.component.scss @@ -0,0 +1,4 @@ +/* this line ensures that switch-style checks are displayed correctly, added here to only affects upload-file-section */ +.form-check-input { + appearance: none; +} \ No newline at end of file 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 c6949bd3828..84f24d2c1eb 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 @@ -62,6 +62,12 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit, */ @Input() isPrimary: boolean | null; + /** + * The indicator if is alternative content bitstream + * @type {boolean, null} + */ + @Input() isAlternativeContent: boolean | null; + /** * The list of available access condition * @type {Array} @@ -282,6 +288,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit, activeModal.componentInstance.pathCombiner = this.pathCombiner; activeModal.componentInstance.submissionId = this.submissionId; activeModal.componentInstance.isPrimary = this.isPrimary; + activeModal.componentInstance.isAlternativeContent = this.isAlternativeContent; } togglePrimaryBitstream(event) { diff --git a/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts b/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts index 792ed087e74..1f33dc811c0 100644 --- a/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts +++ b/src/app/submission/sections/upload/file/themed-section-upload-file.component.ts @@ -27,6 +27,12 @@ export class ThemedSubmissionSectionUploadFileComponent */ @Input() isPrimary: boolean | null; + /** + * The indicator if is alternative content bitstream + * @type {boolean, null} + */ + @Input() isAlternativeContent: boolean | null; + /** * The submission id * @type {string} @@ -80,6 +86,7 @@ export class ThemedSubmissionSectionUploadFileComponent protected inAndOutputNames: (keyof SubmissionSectionUploadFileComponent & keyof this)[] = [ 'availableAccessConditionOptions', 'isPrimary', + 'isAlternativeContent', 'collectionId', 'collectionPolicyType', 'configMetadataForm', diff --git a/src/app/submission/sections/upload/section-upload-constants.ts b/src/app/submission/sections/upload/section-upload-constants.ts index 26f35a094df..06da4b8745a 100644 --- a/src/app/submission/sections/upload/section-upload-constants.ts +++ b/src/app/submission/sections/upload/section-upload-constants.ts @@ -1,2 +1,3 @@ export const POLICY_DEFAULT_NO_LIST = 1; // Banner1 export const POLICY_DEFAULT_WITH_LIST = 2; // Banner2 +export const ALTERNATIVE_CONTENT_TAG = 'alternative content'; // Alternative content tag diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html index 7bcfa1d242d..4928f2ed7e4 100644 --- a/src/app/submission/sections/upload/section-upload.component.html +++ b/src/app/submission/sections/upload/section-upload.component.html @@ -32,6 +32,7 @@ @for (fileEntry of fileList; track fileEntry; let i = $index) { select \"format not in list\" above and describe it under \"Describe new format\".", "bitstream.edit.form.selectedFormat.label": "Selected Format", @@ -3023,6 +3025,8 @@ "item.page.bitstreams.primary": "Primary", + "item.page.bitstreams.alternativeContent": "Alternative Content", + "item.page.filesection.original.bundle": "Original bundle", "item.page.filesection.license.bundle": "License bundle", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 7f1b2f4d6f6..acd5a64f383 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -1409,6 +1409,9 @@ // "bitstream.edit.form.primaryBitstream.label": "Primary File", "bitstream.edit.form.primaryBitstream.label": "Archivo principal", + //"bitstream.edit.form.alternativeContent.label": "Alternative Content File", + "bitstream.edit.form.alternativeContent.label": "Archivo con contenido alternativo", + // "bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".", "bitstream.edit.form.selectedFormat.hint": "Si el formato no está en la lista anterior, seleccione \"el formato no está en la lista\" arriba y descríbalo en \"Describir el nuevo formato\".", @@ -4480,6 +4483,9 @@ // "item.page.bitstreams.primary": "Primary", "item.page.bitstreams.primary": "Principal", + //"item.page.bitstreams.alternativeContent": "Alternative Content", + "item.page.bitstreams.alternativeContent": "Contenido alternativo", + // "item.page.filesection.original.bundle": "Original bundle", "item.page.filesection.original.bundle": "Bloque original",