From f413328a1cd4a5f829807268195b17b1e33ab87c Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 12 Jun 2024 11:44:34 +0200 Subject: [PATCH 01/16] 115491: Refactor retrieval of IIIF status --- .../edit-bitstream-page.component.ts | 80 ++++++++----------- 1 file changed, 35 insertions(+), 45 deletions(-) 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 b77d2151a9b..9402d60523e 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 @@ -403,6 +403,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { getRemoteDataPayload(), ); + const bitstreamFormat$ = bitstream$.pipe( + switchMap((bitstream: Bitstream) => bitstream.format), + getFirstSucceededRemoteDataPayload(), + ); + const allFormats$ = this.bitstreamFormatsRD$.pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), @@ -425,13 +430,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ); this.subs.push( observableCombineLatest( - bitstream$, - allFormats$, - bundle$, - primaryBitstream$, - item$, + [ + bitstream$, + bitstreamFormat$, + allFormats$, + bundle$, + primaryBitstream$, + item$, + ] ).pipe() - .subscribe(([bitstream, allFormats, bundle, primaryBitstream, item]) => { + .subscribe(([bitstream, format, allFormats, bundle, primaryBitstream, item]) => { this.bitstream = bitstream as Bitstream; this.formats = allFormats.page; this.bundle = bundle; @@ -439,7 +447,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { // be a success response, but empty this.primaryBitstreamUUID = hasValue(primaryBitstream) ? primaryBitstream.uuid : null; this.itemId = item.uuid; - this.setIiifStatus(this.bitstream); + this.setIiifStatus(format, bundle, item); }) ); @@ -731,53 +739,35 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { /** * Verifies that the parent item is iiif-enabled. Checks bitstream mimetype to be * sure it's an image, excluding bitstreams in the THUMBNAIL or OTHERCONTENT bundles. - * @param bitstream */ - setIiifStatus(bitstream: Bitstream) { + setIiifStatus(format: BitstreamFormat, bundle: Bundle, item: Item) { const regexExcludeBundles = /OTHERCONTENT|THUMBNAIL|LICENSE/; const regexIIIFItem = /true|yes/i; - const isImage$ = this.bitstream.format.pipe( - getFirstSucceededRemoteData(), - map((format: RemoteData) => format.payload.mimetype.includes('image/'))); + const isImage = format.mimetype.includes('image/'); - const isIIIFBundle$ = this.bitstream.bundle.pipe( - getFirstSucceededRemoteData(), - map((bundle: RemoteData) => - this.dsoNameService.getName(bundle.payload).match(regexExcludeBundles) == null)); + const isIIIFBundle = this.dsoNameService.getName(bundle).match(regexExcludeBundles) === null; - const isEnabled$ = this.bitstream.bundle.pipe( - getFirstSucceededRemoteData(), - map((bundle: RemoteData) => bundle.payload.item.pipe( - getFirstSucceededRemoteData(), - map((item: RemoteData) => - (item.payload.firstMetadataValue('dspace.iiif.enabled') && - item.payload.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null) - )))); - - const iiifSub = combineLatest( - isImage$, - isIIIFBundle$, - isEnabled$ - ).subscribe(([isImage, isIIIFBundle, isEnabled]) => { - if (isImage && isIIIFBundle && isEnabled) { - this.isIIIF = true; - this.inputModels.push(this.iiifLabelModel); - this.formModel.push(this.iiifLabelContainer); - this.inputModels.push(this.iiifTocModel); - this.formModel.push(this.iiifTocContainer); - this.inputModels.push(this.iiifWidthModel); - this.formModel.push(this.iiifWidthContainer); - this.inputModels.push(this.iiifHeightModel); - this.formModel.push(this.iiifHeightContainer); - } - this.setForm(); - this.changeDetectorRef.detectChanges(); - }); + const isEnabled = + item.firstMetadataValue('dspace.iiif.enabled') && + item.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null; - this.subs.push(iiifSub); + this.isIIIF = isImage && isIIIFBundle && isEnabled; + + if (this.isIIIF) { + this.inputModels.push(this.iiifLabelModel); + this.formModel.push(this.iiifLabelContainer); + this.inputModels.push(this.iiifTocModel); + this.formModel.push(this.iiifTocContainer); + this.inputModels.push(this.iiifWidthModel); + this.formModel.push(this.iiifWidthContainer); + this.inputModels.push(this.iiifHeightModel); + this.formModel.push(this.iiifHeightContainer); + } + this.setForm(); + this.changeDetectorRef.detectChanges(); } /** From ff0c1283457eaa00f80d9f2b39eff00c842cbba6 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 12 Jun 2024 13:37:00 +0200 Subject: [PATCH 02/16] 115491: Remove unused route parameters --- .../edit-bitstream-page.component.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 9402d60523e..e33c5643570 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 @@ -3,7 +3,8 @@ import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute, Router } from '@angular/router'; import { filter, map, switchMap, tap } from 'rxjs/operators'; import { combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; +import { + DynamicFormControlEvent, DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; import { UntypedFormGroup } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; @@ -392,9 +393,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * - Translate the form labels and hints */ ngOnInit(): void { - - this.itemId = this.route.snapshot.queryParams.itemId; - this.entityType = this.route.snapshot.queryParams.entityType; this.bitstreamRD$ = this.route.data.pipe(map((data: any) => data.bitstream)); this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions); @@ -729,11 +727,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } /** - * When the item ID is present, navigate back to the item's edit bitstreams page, - * otherwise retrieve the item ID based on the owning bundle's link + * Navigate back to the item's edit bitstreams page */ navigateToItemEditBitstreams() { - this.router.navigate([getEntityEditRoute(this.entityType, this.itemId), 'bitstreams']); + void this.router.navigate([getEntityEditRoute(null, this.itemId), 'bitstreams']); } /** From 9c1624a70fceea199a831f8377eb3f022255b1bf Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 12 Jun 2024 15:56:21 +0200 Subject: [PATCH 03/16] 115491: Combine observables into object --- .../edit-bitstream-page.component.html | 6 +- .../edit-bitstream-page.component.ts | 122 ++++++++++++------ 2 files changed, 82 insertions(+), 46 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 11aa3bfe75e..a99bc0991f7 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 @@ -1,6 +1,6 @@ -
-
+
+
@@ -27,7 +27,7 @@

{{dsoNameService.getName(bitstreamRD?.payload)}} ({

-
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 e33c5643570..24f8862f962 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 @@ -2,15 +2,15 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute, Router } from '@angular/router'; import { filter, map, switchMap, tap } from 'rxjs/operators'; -import { combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; import { - DynamicFormControlEvent, DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; + BehaviorSubject, combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; +import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; import { UntypedFormGroup } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; import cloneDeep from 'lodash/cloneDeep'; import { BitstreamDataService } from '../../core/data/bitstream-data.service'; -import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload } from '../../core/shared/operators'; +import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload, } from '../../core/shared/operators'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service'; import { BitstreamFormat } from '../../core/shared/bitstream-format.model'; @@ -28,6 +28,34 @@ import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-u import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model'; import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service'; +/** + * All observables that have to return their data before the form can be created and filled. Objects of + * the {@link DataObservables} type can directly be used in a 'combineLatest' method to get their values + * once all the observables have fired, and the resulting object will be of the {@link DataObjects} type. + * This interface does not follow the usual convention of appending the dollar ($) symbol to variables + * representing observables, as rxjs will map those names 1-to-1 to the resulting object when used in a + * 'combineLatest' method. + */ +interface DataObservables { + [key: string]: Observable; + bitstream: Observable, + bitstreamFormat: Observable, + bitstreamFormatOptions: Observable>, + bundle: Observable, + primaryBitstream: Observable, + item: Observable, +} + +interface DataObjects { + [key: string]: any; + bitstream: Bitstream, + bitstreamFormat: BitstreamFormat, + bitstreamFormatOptions: PaginatedList, + bundle: Bundle, + primaryBitstream: Bitstream, + item: Item, +} + @Component({ selector: 'ds-edit-bitstream-page', styleUrls: ['./edit-bitstream-page.component.scss'], @@ -39,18 +67,14 @@ import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.servi */ export class EditBitstreamPageComponent implements OnInit, OnDestroy { + isLoading$: BehaviorSubject = new BehaviorSubject(true); + /** * The bitstream's remote data observable * Tracks changes and updates the view */ bitstreamRD$: Observable>; - /** - * The formats their remote data observable - * Tracks changes and updates the view - */ - bitstreamFormatsRD$: Observable>>; - /** * The UUID of the primary bitstream for this bundle */ @@ -394,11 +418,44 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.bitstreamRD$ = this.route.data.pipe(map((data: any) => data.bitstream)); - this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions); + + const dataObservables = this.getDataObservables(); + + this.subs.push( + observableCombineLatest( + dataObservables + ).pipe() + .subscribe((dataObjects: DataObjects) => { + this.isLoading$.next(false); + + this.bitstream = dataObjects.bitstream; + this.formats = dataObjects.bitstreamFormatOptions.page; + this.bundle = dataObjects.bundle; + // hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will + // be a success response, but empty + this.primaryBitstreamUUID = hasValue(dataObjects.primaryBitstream) ? dataObjects.primaryBitstream.uuid : null; + this.itemId = dataObjects.item.uuid; + this.setIiifStatus(dataObjects.bitstreamFormat, dataObjects.bundle, dataObjects.item); + }) + ); + + this.subs.push( + this.translate.onLangChange + .subscribe(() => { + this.updateFieldTranslations(); + }) + ); + } + + /** + * Create all the observables necessary to create and fill the bitstream form, + * and collect them in a {@link DataObservables} object. + */ + protected getDataObservables(): DataObservables { + const bitstreamFormatOptionsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions); const bitstream$ = this.bitstreamRD$.pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), + getFirstSucceededRemoteDataPayload() ); const bitstreamFormat$ = bitstream$.pipe( @@ -406,9 +463,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { getFirstSucceededRemoteDataPayload(), ); - const allFormats$ = this.bitstreamFormatsRD$.pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), + const bitstreamFormatOptions$ = bitstreamFormatOptionsRD$.pipe( + getFirstSucceededRemoteDataPayload() ); const bundle$ = bitstream$.pipe( @@ -426,35 +482,15 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { switchMap((bundle: Bundle) => bundle.item), getFirstSucceededRemoteDataPayload(), ); - this.subs.push( - observableCombineLatest( - [ - bitstream$, - bitstreamFormat$, - allFormats$, - bundle$, - primaryBitstream$, - item$, - ] - ).pipe() - .subscribe(([bitstream, format, allFormats, bundle, primaryBitstream, item]) => { - this.bitstream = bitstream as Bitstream; - this.formats = allFormats.page; - this.bundle = bundle; - // hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will - // be a success response, but empty - this.primaryBitstreamUUID = hasValue(primaryBitstream) ? primaryBitstream.uuid : null; - this.itemId = item.uuid; - this.setIiifStatus(format, bundle, item); - }) - ); - this.subs.push( - this.translate.onLangChange - .subscribe(() => { - this.updateFieldTranslations(); - }) - ); + return { + bitstream: bitstream$, + bitstreamFormat: bitstreamFormat$, + bitstreamFormatOptions: bitstreamFormatOptions$, + bundle: bundle$, + primaryBitstream: primaryBitstream$, + item: item$, + }; } /** From 4fa2017aef363c01ab4602c92abd4ebd6e2bee61 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 13 Jun 2024 08:46:19 +0200 Subject: [PATCH 04/16] 115491: Apply minor code restructuring - Introduced 'setFields' method - Applied more consistency in using fields vs parameters in methods - Split up methods related to iiif --- .../edit-bitstream-page.component.ts | 148 ++++++++++-------- 1 file changed, 86 insertions(+), 62 deletions(-) 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 24f8862f962..65a7ace74fa 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 @@ -3,14 +3,24 @@ import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute, Router } from '@angular/router'; import { filter, map, switchMap, tap } from 'rxjs/operators'; import { - BehaviorSubject, combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; + BehaviorSubject, combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription +} from 'rxjs'; +import { + DynamicFormControlModel, + DynamicFormGroupModel, + DynamicFormLayout, + DynamicFormService, + DynamicInputModel, + DynamicSelectModel +} from '@ng-dynamic-forms/core'; import { UntypedFormGroup } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; -import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; +import { + DynamicCustomSwitchModel +} from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; import cloneDeep from 'lodash/cloneDeep'; import { BitstreamDataService } from '../../core/data/bitstream-data.service'; -import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload, } from '../../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload, } from '../../core/shared/operators'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service'; import { BitstreamFormat } from '../../core/shared/bitstream-format.model'; @@ -38,6 +48,7 @@ import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.servi */ interface DataObservables { [key: string]: Observable; + bitstream: Observable, bitstreamFormat: Observable, bitstreamFormatOptions: Observable>, @@ -48,6 +59,7 @@ interface DataObservables { interface DataObjects { [key: string]: any; + bitstream: Bitstream, bitstreamFormat: BitstreamFormat, bitstreamFormatOptions: PaginatedList, @@ -86,14 +98,19 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { bitstream: Bitstream; /** - * The originally selected format + * The format of the bitstream to edit */ - originalFormat: BitstreamFormat; + bitstreamFormat: BitstreamFormat; /** * A list of all available bitstream formats */ - formats: BitstreamFormat[]; + formatOptions: BitstreamFormat[]; + + /** + * The item that the bitstream belongs to + */ + item: Item; /** * @type {string} Key prefix used to generate form messages @@ -365,13 +382,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ formGroup: UntypedFormGroup; - /** - * The ID of the item the bitstream originates from - * Taken from the current query parameters when present - * This will determine the route of the item edit page to return to - */ - itemId: string; - /** * The entity type of the item the bitstream originates from * Taken from the current query parameters when present @@ -407,7 +417,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { private notificationsService: NotificationsService, private bitstreamFormatService: BitstreamFormatDataService, private primaryBitstreamService: PrimaryBitstreamService, - ) { + ) { } /** @@ -428,14 +438,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { .subscribe((dataObjects: DataObjects) => { this.isLoading$.next(false); - this.bitstream = dataObjects.bitstream; - this.formats = dataObjects.bitstreamFormatOptions.page; - this.bundle = dataObjects.bundle; - // hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will - // be a success response, but empty - this.primaryBitstreamUUID = hasValue(dataObjects.primaryBitstream) ? dataObjects.primaryBitstream.uuid : null; - this.itemId = dataObjects.item.uuid; - this.setIiifStatus(dataObjects.bitstreamFormat, dataObjects.bundle, dataObjects.item); + this.setFields(dataObjects); + + this.setForm(); }) ); @@ -493,24 +498,49 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }; } + /** + * Sets all required fields with the data in the provided dataObjects + * @protected + */ + protected setFields(dataObjects: DataObjects) { + this.bitstream = dataObjects.bitstream; + this.bitstreamFormat = dataObjects.bitstreamFormat; + this.formatOptions = dataObjects.bitstreamFormatOptions.page; + this.bundle = dataObjects.bundle; + // hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will + // be a success response, but empty + this.primaryBitstreamUUID = hasValue(dataObjects.primaryBitstream) ? dataObjects.primaryBitstream.uuid : null; + this.item = dataObjects.item; + + this.isIIIF = this.getIiifStatus(); + } + /** * Initializes the form. */ setForm() { + if (this.isIIIF) { + this.appendFormWIthIiifFields(); + } + this.formGroup = this.formService.createFormGroup(this.formModel); this.updateFormatModel(); - this.updateForm(this.bitstream); + this.updateForm(); this.updateFieldTranslations(); + + this.changeDetectorRef.detectChanges(); } /** - * Update the current form values with bitstream properties - * @param bitstream + * Update the current form values with the current bitstream properties */ - updateForm(bitstream: Bitstream) { + updateForm() { + const bitstream = this.bitstream; + const format = this.bitstreamFormat; + this.formGroup.patchValue({ fileNamePrimaryContainer: { - fileName: bitstream.name, + fileName: this.dsoNameService.getName(bitstream), primaryBitstream: this.primaryBitstreamUUID === bitstream.uuid }, descriptionContainer: { @@ -536,24 +566,21 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } }); } - this.bitstream.format.pipe( - getAllSucceededRemoteDataPayload() - ).subscribe((format: BitstreamFormat) => { - this.originalFormat = format; - this.formGroup.patchValue({ - formatContainer: { - selectedFormat: format.id - } - }); - this.updateNewFormatLayout(format.id); + + this.bitstreamFormat = format; + this.formGroup.patchValue({ + formatContainer: { + selectedFormat: format.id + } }); + this.updateNewFormatLayout(format.id); } /** * Create the list of unknown format IDs an add options to the selectedFormatModel */ updateFormatModel() { - this.selectedFormatModel.options = this.formats.map((format: BitstreamFormat) => + this.selectedFormatModel.options = this.formatOptions.map((format: BitstreamFormat) => Object.assign({ value: format.id, label: this.isUnknownFormat(format.id) ? this.translate.instant(this.KEY_PREFIX + 'selectedFormat.unknown') : format.shortDescription @@ -577,7 +604,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * @param id */ isUnknownFormat(id: string): boolean { - const format = this.formats.find((f: BitstreamFormat) => f.id === id); + const format = this.formatOptions.find((f: BitstreamFormat) => f.id === id); return hasValue(format) && format.supportLevel === BitstreamFormatSupportLevel.Unknown; } @@ -620,8 +647,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { onSubmit() { const updatedValues = this.formGroup.getRawValue(); const updatedBitstream = this.formToBitstream(updatedValues); - const selectedFormat = this.formats.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat); - const isNewFormat = selectedFormat.id !== this.originalFormat.id; + const selectedFormat = this.formatOptions.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat); + const isNewFormat = selectedFormat.id !== this.bitstreamFormat.id; const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream; const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid; @@ -766,41 +793,38 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * Navigate back to the item's edit bitstreams page */ navigateToItemEditBitstreams() { - void this.router.navigate([getEntityEditRoute(null, this.itemId), 'bitstreams']); + void this.router.navigate([getEntityEditRoute(null, this.item.uuid), 'bitstreams']); } /** * Verifies that the parent item is iiif-enabled. Checks bitstream mimetype to be * sure it's an image, excluding bitstreams in the THUMBNAIL or OTHERCONTENT bundles. */ - setIiifStatus(format: BitstreamFormat, bundle: Bundle, item: Item) { + getIiifStatus(): boolean { const regexExcludeBundles = /OTHERCONTENT|THUMBNAIL|LICENSE/; const regexIIIFItem = /true|yes/i; - const isImage = format.mimetype.includes('image/'); + const isImage = this.bitstreamFormat.mimetype.includes('image/'); - const isIIIFBundle = this.dsoNameService.getName(bundle).match(regexExcludeBundles) === null; + const isIIIFBundle = this.dsoNameService.getName(this.bundle).match(regexExcludeBundles) === null; const isEnabled = - item.firstMetadataValue('dspace.iiif.enabled') && - item.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null; - - this.isIIIF = isImage && isIIIFBundle && isEnabled; + this.item.firstMetadataValue('dspace.iiif.enabled') && + this.item.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null; - if (this.isIIIF) { - this.inputModels.push(this.iiifLabelModel); - this.formModel.push(this.iiifLabelContainer); - this.inputModels.push(this.iiifTocModel); - this.formModel.push(this.iiifTocContainer); - this.inputModels.push(this.iiifWidthModel); - this.formModel.push(this.iiifWidthContainer); - this.inputModels.push(this.iiifHeightModel); - this.formModel.push(this.iiifHeightContainer); - } + return isImage && isIIIFBundle && isEnabled; + } - this.setForm(); - this.changeDetectorRef.detectChanges(); + appendFormWIthIiifFields(): void { + this.inputModels.push(this.iiifLabelModel); + this.formModel.push(this.iiifLabelContainer); + this.inputModels.push(this.iiifTocModel); + this.formModel.push(this.iiifTocContainer); + this.inputModels.push(this.iiifWidthModel); + this.formModel.push(this.iiifWidthContainer); + this.inputModels.push(this.iiifHeightModel); + this.formModel.push(this.iiifHeightContainer); } /** From 893c666d9312b25e31720dae496eca3d042ff2b4 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 13 Jun 2024 10:19:30 +0200 Subject: [PATCH 05/16] 115491: Turn constant fields into actual constants --- .../edit-bitstream-page.component.ts | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) 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 65a7ace74fa..254cadc2de5 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 @@ -68,6 +68,46 @@ interface DataObjects { item: Item, } +/** + * Key prefix used to generate form messages + */ +const KEY_PREFIX = 'bitstream.edit.form.'; + +/** + * Key suffix used to generate form labels + */ +const LABEL_KEY_SUFFIX = '.label'; + +/** + * Key suffix used to generate form labels + */ +const HINT_KEY_SUFFIX = '.hint'; + +/** + * Key prefix used to generate notification messages + */ +const NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.'; + +/** + * IIIF image width metadata key + */ +const IMAGE_WIDTH_METADATA = 'iiif.image.width'; + +/** + * IIIF image height metadata key + */ +const IMAGE_HEIGHT_METADATA = 'iiif.image.height'; + +/** + * IIIF table of contents metadata key + */ +const IIIF_TOC_METADATA = 'iiif.toc'; + +/** + * IIIF label metadata key + */ +const IIIF_LABEL_METADATA = 'iiif.label'; + @Component({ selector: 'ds-edit-bitstream-page', styleUrls: ['./edit-bitstream-page.component.scss'], @@ -112,46 +152,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ item: Item; - /** - * @type {string} Key prefix used to generate form messages - */ - KEY_PREFIX = 'bitstream.edit.form.'; - - /** - * @type {string} Key suffix used to generate form labels - */ - LABEL_KEY_SUFFIX = '.label'; - - /** - * @type {string} Key suffix used to generate form labels - */ - HINT_KEY_SUFFIX = '.hint'; - - /** - * @type {string} Key prefix used to generate notification messages - */ - NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.'; - - /** - * IIIF image width metadata key - */ - IMAGE_WIDTH_METADATA = 'iiif.image.width'; - - /** - * IIIF image height metadata key - */ - IMAGE_HEIGHT_METADATA = 'iiif.image.height'; - - /** - * IIIF table of contents metadata key - */ - IIIF_TOC_METADATA = 'iiif.toc'; - - /** - * IIIF label metadata key - */ - IIIF_LABEL_METADATA = 'iiif.label'; - /** * Options for fetching all bitstream formats */ @@ -553,16 +553,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { if (this.isIIIF) { this.formGroup.patchValue({ iiifLabelContainer: { - iiifLabel: bitstream.firstMetadataValue(this.IIIF_LABEL_METADATA) + iiifLabel: bitstream.firstMetadataValue(IIIF_LABEL_METADATA) }, iiifTocContainer: { - iiifToc: bitstream.firstMetadataValue(this.IIIF_TOC_METADATA) + iiifToc: bitstream.firstMetadataValue(IIIF_TOC_METADATA) }, iiifWidthContainer: { - iiifWidth: bitstream.firstMetadataValue(this.IMAGE_WIDTH_METADATA) + iiifWidth: bitstream.firstMetadataValue(IMAGE_WIDTH_METADATA) }, iiifHeightContainer: { - iiifHeight: bitstream.firstMetadataValue(this.IMAGE_HEIGHT_METADATA) + iiifHeight: bitstream.firstMetadataValue(IMAGE_HEIGHT_METADATA) } }); } @@ -583,7 +583,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { this.selectedFormatModel.options = this.formatOptions.map((format: BitstreamFormat) => Object.assign({ value: format.id, - label: this.isUnknownFormat(format.id) ? this.translate.instant(this.KEY_PREFIX + 'selectedFormat.unknown') : format.shortDescription + label: this.isUnknownFormat(format.id) ? this.translate.instant(KEY_PREFIX + 'selectedFormat.unknown') : format.shortDescription })); } @@ -624,9 +624,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * @param fieldModel */ private updateFieldTranslation(fieldModel) { - fieldModel.label = this.translate.instant(this.KEY_PREFIX + fieldModel.id + this.LABEL_KEY_SUFFIX); + fieldModel.label = this.translate.instant(KEY_PREFIX + fieldModel.id + LABEL_KEY_SUFFIX); if (fieldModel.id !== this.primaryBitstreamModel.id) { - fieldModel.hint = this.translate.instant(this.KEY_PREFIX + fieldModel.id + this.HINT_KEY_SUFFIX); + fieldModel.hint = this.translate.instant(KEY_PREFIX + fieldModel.id + HINT_KEY_SUFFIX); } } @@ -672,7 +672,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { filter((bundleRd: RemoteData) => bundleRd.hasFailed) ).subscribe((bundleRd: RemoteData) => { this.notificationsService.error( - this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), bundleRd.errorMessage ); errorWhileSaving = true; @@ -705,7 +705,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { map((formatResponse: RemoteData) => { if (hasValue(formatResponse) && formatResponse.hasFailed) { this.notificationsService.error( - this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.format.title'), + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.format.title'), formatResponse.errorMessage ); } else { @@ -727,8 +727,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ).subscribe(() => { this.bitstreamService.commitUpdates(); this.notificationsService.success( - this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.title'), - this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.content') + this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.title'), + this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.content') ); if (!errorWhileSaving) { this.navigateToItemEditBitstreams(); @@ -755,24 +755,24 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { // remove an existing "table of contents" entry. if (isEmpty(rawForm.iiifLabelContainer.iiifLabel)) { - delete newMetadata[this.IIIF_LABEL_METADATA]; + delete newMetadata[IIIF_LABEL_METADATA]; } else { - Metadata.setFirstValue(newMetadata, this.IIIF_LABEL_METADATA, rawForm.iiifLabelContainer.iiifLabel); + Metadata.setFirstValue(newMetadata, IIIF_LABEL_METADATA, rawForm.iiifLabelContainer.iiifLabel); } if (isEmpty(rawForm.iiifTocContainer.iiifToc)) { - delete newMetadata[this.IIIF_TOC_METADATA]; + delete newMetadata[IIIF_TOC_METADATA]; } else { - Metadata.setFirstValue(newMetadata, this.IIIF_TOC_METADATA, rawForm.iiifTocContainer.iiifToc); + Metadata.setFirstValue(newMetadata, IIIF_TOC_METADATA, rawForm.iiifTocContainer.iiifToc); } if (isEmpty(rawForm.iiifWidthContainer.iiifWidth)) { - delete newMetadata[this.IMAGE_WIDTH_METADATA]; + delete newMetadata[IMAGE_WIDTH_METADATA]; } else { - Metadata.setFirstValue(newMetadata, this.IMAGE_WIDTH_METADATA, rawForm.iiifWidthContainer.iiifWidth); + Metadata.setFirstValue(newMetadata, IMAGE_WIDTH_METADATA, rawForm.iiifWidthContainer.iiifWidth); } if (isEmpty(rawForm.iiifHeightContainer.iiifHeight)) { - delete newMetadata[this.IMAGE_HEIGHT_METADATA]; + delete newMetadata[IMAGE_HEIGHT_METADATA]; } else { - Metadata.setFirstValue(newMetadata, this.IMAGE_HEIGHT_METADATA, rawForm.iiifHeightContainer.iiifHeight); + Metadata.setFirstValue(newMetadata, IMAGE_HEIGHT_METADATA, rawForm.iiifHeightContainer.iiifHeight); } } if (isNotEmpty(rawForm.formatContainer.newFormat)) { From 16c75dbd3a99a6741ee9fed00446e203e6ec1323 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 13 Jun 2024 10:50:37 +0200 Subject: [PATCH 06/16] 115491: Split form creation into multiple methods Splitting the creation of the form into multiple methods that do one specific thing (e.g. updating the form model, creating the form group, prefilling data in the form, ...) allows for easier theming of the form itself. --- .../edit-bitstream-page.component.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) 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 254cadc2de5..079c39773ae 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 @@ -13,7 +13,7 @@ import { DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core'; -import { UntypedFormGroup } from '@angular/forms'; +import { UntypedFormGroup, FormGroup } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { DynamicCustomSwitchModel @@ -519,18 +519,33 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * Initializes the form. */ setForm() { - if (this.isIIIF) { - this.appendFormWIthIiifFields(); - } + this.updateFormModel(); + this.formGroup = this.getFormGroup(); - this.formGroup = this.formService.createFormGroup(this.formModel); - this.updateFormatModel(); this.updateForm(); this.updateFieldTranslations(); this.changeDetectorRef.detectChanges(); } + /** + * Updates the formModel with additional fields & options, depending on the current data + */ + updateFormModel() { + this.updateFormatModel(); + + if (this.isIIIF) { + this.appendFormWIthIiifFields(); + } + } + + /** + * Creates a formGroup from the current formModel + */ + getFormGroup(): FormGroup { + return this.formService.createFormGroup(this.formModel); + } + /** * Update the current form values with the current bitstream properties */ From e83ad087c858bac6c4fbc1d3ece241bcea4706af Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 13 Jun 2024 11:47:45 +0200 Subject: [PATCH 07/16] 115491: Track subscription --- .../edit-bitstream-page/edit-bitstream-page.component.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 079c39773ae..a8ee3394301 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 @@ -667,7 +667,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream; const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid; - let bitstream$; + let bitstream$: Observable; let bundle$: Observable; let errorWhileSaving = false; @@ -714,6 +714,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } else { bundle$ = observableOf(this.bundle); } + if (isNewFormat) { bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe( getFirstCompletedRemoteData(), @@ -732,7 +733,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { bitstream$ = observableOf(this.bitstream); } - combineLatest([bundle$, bitstream$]).pipe( + this.subs.push(combineLatest([bundle$, bitstream$]).pipe( tap(([bundle]) => this.bundle = bundle), switchMap(() => { return this.bitstreamService.update(updatedBitstream).pipe( @@ -748,7 +749,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { if (!errorWhileSaving) { this.navigateToItemEditBitstreams(); } - }); + })); } /** From 66823ced4e918ea119ffd6d37a05b525ae1d34db Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 14 Jun 2024 08:47:54 +0200 Subject: [PATCH 08/16] 115491: Integrate subscription into pipe --- .../edit-bitstream-page.component.ts | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) 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 a8ee3394301..3ae46b393e7 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 @@ -681,36 +681,37 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { bundleRd$ = this.primaryBitstreamService.create(this.bitstream, this.bundle); } - const completedBundleRd$ = bundleRd$.pipe(getFirstCompletedRemoteData()); - - this.subs.push(completedBundleRd$.pipe( - filter((bundleRd: RemoteData) => bundleRd.hasFailed) - ).subscribe((bundleRd: RemoteData) => { - this.notificationsService.error( - this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), - bundleRd.errorMessage - ); - errorWhileSaving = true; - })); - - bundle$ = completedBundleRd$.pipe( - map((bundleRd: RemoteData) => { + bundle$ = bundleRd$.pipe( + getFirstCompletedRemoteData(), + // If the request succeeded, use the new bundle data + // Otherwise send a notification and use the old bundle data + switchMap((bundleRd: RemoteData) => { if (bundleRd.hasSucceeded) { - return bundleRd.payload; + return observableOf(bundleRd.payload); } else { - return this.bundle; + this.notificationsService.error( + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), + bundleRd.errorMessage + ); + errorWhileSaving = true; + + return observableOf(this.bundle); } - }) + }), + // Set the primary bitstream ID depending on the available bundle data + switchMap((bundle) => { + return this.bitstreamService.findByHref(bundle._links.primaryBitstream.href, false).pipe( + getFirstSucceededRemoteDataPayload(), + tap((bitstream: Bitstream) => { + this.primaryBitstreamUUID = hasValue(bitstream) ? bitstream.uuid : null; + }), + map((_) => { + return bundle; + }) + ); + }), ); - this.subs.push(bundle$.pipe( - hasValueOperator(), - switchMap((bundle: Bundle) => this.bitstreamService.findByHref(bundle._links.primaryBitstream.href, false)), - getFirstSucceededRemoteDataPayload() - ).subscribe((bitstream: Bitstream) => { - this.primaryBitstreamUUID = hasValue(bitstream) ? bitstream.uuid : null; - })); - } else { bundle$ = observableOf(this.bundle); } From 23a29bac7bab9004a302e91b0752ae670b9fca54 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 14 Jun 2024 11:14:31 +0200 Subject: [PATCH 09/16] 115491: Always retrieve fresh data Without this fix the form showed old data when immediately going back to the edit page after changing the primary bitstream or format. --- .../edit-bitstream-page.component.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) 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 3ae46b393e7..70e908e4996 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 @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute, Router } from '@angular/router'; -import { filter, map, switchMap, tap } from 'rxjs/operators'; +import { map, switchMap, tap } from 'rxjs/operators'; import { BehaviorSubject, combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; @@ -464,7 +464,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ); const bitstreamFormat$ = bitstream$.pipe( - switchMap((bitstream: Bitstream) => bitstream.format), + switchMap((bitstream: Bitstream) => this.bitstreamFormatService.findByHref(bitstream._links.format.href, false)), getFirstSucceededRemoteDataPayload(), ); @@ -479,7 +479,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { const primaryBitstream$ = bundle$.pipe( hasValueOperator(), - switchMap((bundle: Bundle) => this.bitstreamService.findByHref(bundle._links.primaryBitstream.href)), + switchMap((bundle: Bundle) => this.bitstreamService.findByHref(bundle._links.primaryBitstream.href, false)), getFirstSucceededRemoteDataPayload(), ); @@ -698,18 +698,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { return observableOf(this.bundle); } }), - // Set the primary bitstream ID depending on the available bundle data - switchMap((bundle) => { - return this.bitstreamService.findByHref(bundle._links.primaryBitstream.href, false).pipe( - getFirstSucceededRemoteDataPayload(), - tap((bitstream: Bitstream) => { - this.primaryBitstreamUUID = hasValue(bitstream) ? bitstream.uuid : null; - }), - map((_) => { - return bundle; - }) - ); - }), ); } else { From 1c93e4f3ee4c9fb17cb984cbf736a38a19e5a061 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 14 Jun 2024 11:47:12 +0200 Subject: [PATCH 10/16] 115491: Simplify update logic Moved the creation of the update observables to their own methods and unified the handling of their results in a single subscription --- .../edit-bitstream-page.component.ts | 164 ++++++++++-------- 1 file changed, 93 insertions(+), 71 deletions(-) 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 70e908e4996..b798b58f4b1 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 @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute, Router } from '@angular/router'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { BehaviorSubject, combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; @@ -661,84 +661,106 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ onSubmit() { const updatedValues = this.formGroup.getRawValue(); + + const metadataUpdateRD$ = this.updateBitstreamMetadataRD$(updatedValues); + const primaryUpdateRD$ = this.updatePrimaryBitstreamRD$(updatedValues); + const formatUpdateRD$ = this.updateBitstreamFormatRD$(updatedValues); + + this.subs.push(combineLatest([metadataUpdateRD$, primaryUpdateRD$, formatUpdateRD$]) + .subscribe(([metadataUpdateRD, primaryUpdateRD, formatUpdateRD]) => { + let errorWhileSaving = false; + + // Check for errors during the primary bitstream update + if (hasValue(primaryUpdateRD) && primaryUpdateRD.hasFailed) { + this.notificationsService.error( + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), + primaryUpdateRD.errorMessage + ); + + errorWhileSaving = true; + } + + // Check for errors during the bitstream format update + if (hasValue(formatUpdateRD) && formatUpdateRD.hasFailed) { + this.notificationsService.error( + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.format.title'), + formatUpdateRD.errorMessage + ); + + errorWhileSaving = true; + } + + this.bitstreamService.commitUpdates(); + this.notificationsService.success( + this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.title'), + this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.content') + ); + if (!errorWhileSaving) { + this.navigateToItemEditBitstreams(); + } + }) + ); + } + + updateBitstreamMetadataRD$(updatedValues: any): Observable> { const updatedBitstream = this.formToBitstream(updatedValues); - const selectedFormat = this.formatOptions.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat); - const isNewFormat = selectedFormat.id !== this.bitstreamFormat.id; - const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream; - const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid; - - let bitstream$: Observable; - let bundle$: Observable; - let errorWhileSaving = false; - - if (wasPrimary !== isPrimary) { - let bundleRd$: Observable>; - if (wasPrimary) { - bundleRd$ = this.primaryBitstreamService.delete(this.bundle); - } else if (hasValue(this.primaryBitstreamUUID)) { - bundleRd$ = this.primaryBitstreamService.put(this.bitstream, this.bundle); - } else { - bundleRd$ = this.primaryBitstreamService.create(this.bitstream, this.bundle); - } - bundle$ = bundleRd$.pipe( - getFirstCompletedRemoteData(), - // If the request succeeded, use the new bundle data - // Otherwise send a notification and use the old bundle data - switchMap((bundleRd: RemoteData) => { - if (bundleRd.hasSucceeded) { - return observableOf(bundleRd.payload); - } else { - this.notificationsService.error( - this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), - bundleRd.errorMessage - ); - errorWhileSaving = true; - - return observableOf(this.bundle); - } - }), - ); + return this.bitstreamService.update(updatedBitstream).pipe( + getFirstCompletedRemoteData() + ); + } + + /** + * Creates and returns an observable that will update the primary bitstream in the bundle of the + * current bitstream, if necessary according to the provided updated values. + * When an update is necessary, the observable fires once with the completed RemoteData of the bundle update. + * When no update is necessary, the observable fires once with a null value. + * @param updatedValues The raw updated values in the bitstream edit form + */ + updatePrimaryBitstreamRD$(updatedValues: any): Observable> { + // Whether the edited bitstream should be the primary bitstream according to the form + const shouldBePrimary: boolean = updatedValues.fileNamePrimaryContainer.primaryBitstream; + // Whether the edited bitstream currently is the primary bitstream + const isPrimary = this.primaryBitstreamUUID === this.bitstream.uuid; - } else { - bundle$ = observableOf(this.bundle); + // If the primary bitstream status should not be changed, there is nothing to do + if (shouldBePrimary === isPrimary) { + return observableOf(null); } - if (isNewFormat) { - bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe( - getFirstCompletedRemoteData(), - map((formatResponse: RemoteData) => { - if (hasValue(formatResponse) && formatResponse.hasFailed) { - this.notificationsService.error( - this.translate.instant(NOTIFICATIONS_PREFIX + 'error.format.title'), - formatResponse.errorMessage - ); - } else { - return formatResponse.payload; - } - }) - ); + let updatedBundleRD$: Observable>; + if (isPrimary) { + updatedBundleRD$ = this.primaryBitstreamService.delete(this.bundle); + } else if (hasValue(this.primaryBitstreamUUID)) { + updatedBundleRD$ = this.primaryBitstreamService.put(this.bitstream, this.bundle); } else { - bitstream$ = observableOf(this.bitstream); + updatedBundleRD$ = this.primaryBitstreamService.create(this.bitstream, this.bundle); } - this.subs.push(combineLatest([bundle$, bitstream$]).pipe( - tap(([bundle]) => this.bundle = bundle), - switchMap(() => { - return this.bitstreamService.update(updatedBitstream).pipe( - getFirstSucceededRemoteDataPayload() - ); - }) - ).subscribe(() => { - this.bitstreamService.commitUpdates(); - this.notificationsService.success( - this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.title'), - this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.content') - ); - if (!errorWhileSaving) { - this.navigateToItemEditBitstreams(); - } - })); + return updatedBundleRD$.pipe( + getFirstCompletedRemoteData() + ); + } + + /** + * Creates and returns an observable that will update the bitstream format + * if necessary according to the provided updated values. + * When an update is necessary, the observable fires once with the completed RemoteData of the bitstream update. + * When no update is necessary, the observable fires once with a null value. + * @param updatedValues The raw updated values in the bitstream edit form + */ + updateBitstreamFormatRD$(updatedValues: any): Observable> { + const selectedFormat = this.formatOptions.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat); + const formatChanged = selectedFormat.id !== this.bitstreamFormat.id; + + // If the format has not changed, there is nothing to do + if (!formatChanged) { + return observableOf(null); + } + + return this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe( + getFirstCompletedRemoteData(), + ); } /** From 6195b8d4cd6f8ec8f05744734cbda1b2d576f950 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 14 Jun 2024 15:18:46 +0200 Subject: [PATCH 11/16] 115491: Make better use of the type system --- .../edit-bitstream-page.component.ts | 25 ++----- .../shared/utils/observables-dictionary.ts | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 src/app/shared/utils/observables-dictionary.ts 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 b798b58f4b1..b8c1d600361 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 @@ -37,29 +37,12 @@ import { Item } from '../../core/shared/item.model'; import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model'; import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model'; import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service'; +import { ObservablesDictionary } from 'src/app/shared/utils/observables-dictionary'; /** - * All observables that have to return their data before the form can be created and filled. Objects of - * the {@link DataObservables} type can directly be used in a 'combineLatest' method to get their values - * once all the observables have fired, and the resulting object will be of the {@link DataObjects} type. - * This interface does not follow the usual convention of appending the dollar ($) symbol to variables - * representing observables, as rxjs will map those names 1-to-1 to the resulting object when used in a - * 'combineLatest' method. + * All data that is required before the form can be created and filled. */ -interface DataObservables { - [key: string]: Observable; - - bitstream: Observable, - bitstreamFormat: Observable, - bitstreamFormatOptions: Observable>, - bundle: Observable, - primaryBitstream: Observable, - item: Observable, -} - interface DataObjects { - [key: string]: any; - bitstream: Bitstream, bitstreamFormat: BitstreamFormat, bitstreamFormatOptions: PaginatedList, @@ -454,9 +437,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { /** * Create all the observables necessary to create and fill the bitstream form, - * and collect them in a {@link DataObservables} object. + * and collect them in a {@link ObservablesDictionary} object. */ - protected getDataObservables(): DataObservables { + protected getDataObservables(): ObservablesDictionary { const bitstreamFormatOptionsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions); const bitstream$ = this.bitstreamRD$.pipe( diff --git a/src/app/shared/utils/observables-dictionary.ts b/src/app/shared/utils/observables-dictionary.ts new file mode 100644 index 00000000000..ac9cf907b9c --- /dev/null +++ b/src/app/shared/utils/observables-dictionary.ts @@ -0,0 +1,72 @@ +import { Observable } from 'rxjs'; + +/** + * Utility type that allows stricter type checking when creating a method with output that is intended to be used + * in a 'combineLatest' or similar call. + */ +export type ObservablesDictionary = { + [key in keyof T]: Observable +}; + +/* + How to use an ObservablesDictionary: + + Suppose that you require multiple observables to fire before you can start with a task such as creating a form. The + usual way to implement this is to create all the necessary observables, combine them in an array, pass the array as + argument in a 'combineLatest' call, and subscribe to the resulting observable to handle the result. + + Having to deconstruct the array into its components can be tedious and error-prone. RxJS supports dictionaries of + observables as input argument in 'combineLatest', so it would be nice to be able to use this while maximally making + use of TypeScript's type safety. That is where the ObservablesDictionary type comes in. + + You start by defining the interface that should be the output of the 'combineLatest' method. + e.g.: + + interface MyData { + collection: Collection; + bitstreams: PaginatedList; + title: string; + } + + Now the input for the 'combineLatest' should be of type ObservablesDictionary. + In essence ObservablesDictionary creates a copy of the defined interface T, while making observables of all T's + fields. ObservablesDictionary also applies the additional constraint that all the keys of T must be strings, which + is required for objects used in 'combineLatest'. + + ObservablesDictionary is equivalent to the following: + + interface ObservablesDictionaryMyData { + collection: Observable; + bitstreams: Observable>; + title: Observable; + } + + This does not follow the convention of appending fieldNames of observables with the dollar sign ($). This is because + RxJS maps the input names one-to-one to the output names, so they must be exactly the same. + + + By using these types it becomes much easier to separate the process into multiple parts while maximally making use of + the type system: The first function creates all the necessary observables and returns an object of type + ObservablesDictionary. The second function takes as argument an object of type MyData and performs whatever + action you want to with the retrieved data. The final function then simply handles the necessary plumbing by calling + the first method, placing the result as argument in a 'combineLatest' method, and in the subscription simply passing + the result through to the second function. + + + + An example of this type in action can be found in the edit-bitstream-page component (as of writing this explainer). + The edit-bitstream-page has the following interface with that contains the required data: + + interface DataObjects { + bitstream: Bitstream, + bitstreamFormat: BitstreamFormat, + bitstreamFormatOptions: PaginatedList, + bundle: Bundle, + primaryBitstream: Bitstream, + item: Item, + } + + The getDataObservables provides all the observables in an ObservablesDictionary object + which is used in the ngOnInit method to retrieve all the data necessary to create the edit form. +*/ + From ff2fbf119443cca527f2494fe6723043ee4ada86 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 14 Jun 2024 15:31:15 +0200 Subject: [PATCH 12/16] 115491: Restructure bitstream update code --- .../edit-bitstream-page.component.ts | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) 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 b8c1d600361..01f52086cd2 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 @@ -51,6 +51,15 @@ interface DataObjects { item: Item, } +/** + * The results after updating all the fields on submission. + */ +interface UpdateResult { + metadataUpdateRD: RemoteData, + primaryUpdateRD: RemoteData, + formatUpdateRD: RemoteData, +} + /** * Key prefix used to generate form messages */ @@ -645,46 +654,21 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { onSubmit() { const updatedValues = this.formGroup.getRawValue(); - const metadataUpdateRD$ = this.updateBitstreamMetadataRD$(updatedValues); - const primaryUpdateRD$ = this.updatePrimaryBitstreamRD$(updatedValues); - const formatUpdateRD$ = this.updateBitstreamFormatRD$(updatedValues); - - this.subs.push(combineLatest([metadataUpdateRD$, primaryUpdateRD$, formatUpdateRD$]) - .subscribe(([metadataUpdateRD, primaryUpdateRD, formatUpdateRD]) => { - let errorWhileSaving = false; - - // Check for errors during the primary bitstream update - if (hasValue(primaryUpdateRD) && primaryUpdateRD.hasFailed) { - this.notificationsService.error( - this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), - primaryUpdateRD.errorMessage - ); - - errorWhileSaving = true; - } - - // Check for errors during the bitstream format update - if (hasValue(formatUpdateRD) && formatUpdateRD.hasFailed) { - this.notificationsService.error( - this.translate.instant(NOTIFICATIONS_PREFIX + 'error.format.title'), - formatUpdateRD.errorMessage - ); - - errorWhileSaving = true; - } - - this.bitstreamService.commitUpdates(); - this.notificationsService.success( - this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.title'), - this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.content') - ); - if (!errorWhileSaving) { - this.navigateToItemEditBitstreams(); - } + this.subs.push(combineLatest(this.getUpdateObservables(updatedValues)) + .subscribe((updateResult: UpdateResult) => { + this.handleUpdateResult(updateResult); }) ); } + getUpdateObservables(updatedValues: any): ObservablesDictionary { + return { + metadataUpdateRD: this.updateBitstreamMetadataRD$(updatedValues), + primaryUpdateRD: this.updatePrimaryBitstreamRD$(updatedValues), + formatUpdateRD: this.updateBitstreamFormatRD$(updatedValues), + }; + } + updateBitstreamMetadataRD$(updatedValues: any): Observable> { const updatedBitstream = this.formToBitstream(updatedValues); @@ -792,6 +776,42 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { return updatedBitstream; } + handleUpdateResult(updateResult: UpdateResult) { + let errorWhileSaving = false; + + // Check for errors during the primary bitstream update + const primaryUpdateRD = updateResult.primaryUpdateRD; + if (hasValue(primaryUpdateRD) && primaryUpdateRD.hasFailed) { + this.notificationsService.error( + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'), + primaryUpdateRD.errorMessage + ); + + errorWhileSaving = true; + } + + // Check for errors during the bitstream format update + const formatUpdateRD = updateResult.formatUpdateRD; + if (hasValue(formatUpdateRD) && formatUpdateRD.hasFailed) { + this.notificationsService.error( + this.translate.instant(NOTIFICATIONS_PREFIX + 'error.format.title'), + formatUpdateRD.errorMessage + ); + + errorWhileSaving = true; + } + + this.bitstreamService.commitUpdates(); + this.notificationsService.success( + this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.title'), + this.translate.instant(NOTIFICATIONS_PREFIX + 'saved.content') + ); + + if (!errorWhileSaving) { + this.navigateToItemEditBitstreams(); + } + } + /** * Cancel the form and return to the previous page */ From 28fe5ae3a6f121c901b86ac5b170c60025d22a6e Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 14 Jun 2024 16:21:21 +0200 Subject: [PATCH 13/16] 115491: Fix test after refactor --- .../edit-bitstream-page.component.spec.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 b83f2b96643..64d6042ca08 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 @@ -102,7 +102,8 @@ describe('EditBitstreamPageComponent', () => { }); bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { - findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) + findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)), + findByHref: createSuccessfulRemoteDataObject$(selectedFormat), }); notificationsService = jasmine.createSpyObj('notificationsService', @@ -138,6 +139,7 @@ describe('EditBitstreamPageComponent', () => { }); describe('EditBitstreamPageComponent no IIIF fields', () => { + const dsoNameServiceReturnValue = 'ORIGINAL'; beforeEach(waitForAsync(() => { bundle = { @@ -153,7 +155,6 @@ describe('EditBitstreamPageComponent', () => { }, })) }; - const bundleName = 'ORIGINAL'; bitstream = Object.assign(new Bitstream(), { uuid: bitstreamID, @@ -172,7 +173,8 @@ describe('EditBitstreamPageComponent', () => { }, format: createSuccessfulRemoteDataObject$(selectedFormat), _links: { - self: 'bitstream-selflink' + self: 'bitstream-selflink', + format: 'format-link', }, bundle: createSuccessfulRemoteDataObject$(bundle) }); @@ -185,10 +187,11 @@ describe('EditBitstreamPageComponent', () => { patch: {} }); bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { - findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) + findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)), + findByHref: createSuccessfulRemoteDataObject$(selectedFormat), }); dsoNameService = jasmine.createSpyObj('dsoNameService', { - getName: bundleName + getName: dsoNameServiceReturnValue, }); TestBed.configureTestingModule({ @@ -231,7 +234,7 @@ describe('EditBitstreamPageComponent', () => { }); it('should fill in the bitstream\'s title', () => { - expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(bitstream.name); + expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(dsoNameServiceReturnValue); }); it('should fill in the bitstream\'s description', () => { @@ -403,7 +406,7 @@ describe('EditBitstreamPageComponent', () => { }); describe('when navigateToItemEditBitstreams is called', () => { it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => { - comp.itemId = 'some-uuid1'; + comp.item.uuid = 'some-uuid1'; comp.navigateToItemEditBitstreams(); expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']); }); @@ -451,7 +454,8 @@ describe('EditBitstreamPageComponent', () => { }, format: createSuccessfulRemoteDataObject$(allFormats[1]), _links: { - self: 'bitstream-selflink' + self: 'bitstream-selflink', + format: 'format-link', }, bundle: createSuccessfulRemoteDataObject$({ _links: { @@ -576,7 +580,8 @@ describe('EditBitstreamPageComponent', () => { }, format: createSuccessfulRemoteDataObject$(allFormats[2]), _links: { - self: 'bitstream-selflink' + self: 'bitstream-selflink', + format: 'format-link', }, bundle: createSuccessfulRemoteDataObject$({ _links: { From f0782e8e9340fc3696669135c6f18cf8bbbcbc54 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Mon, 17 Jun 2024 08:21:42 +0200 Subject: [PATCH 14/16] 115491: Export interfaces --- .../edit-bitstream-page/edit-bitstream-page.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 01f52086cd2..d941bb4b156 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 @@ -42,7 +42,7 @@ import { ObservablesDictionary } from 'src/app/shared/utils/observables-dictiona /** * All data that is required before the form can be created and filled. */ -interface DataObjects { +export interface DataObjects { bitstream: Bitstream, bitstreamFormat: BitstreamFormat, bitstreamFormatOptions: PaginatedList, @@ -54,7 +54,7 @@ interface DataObjects { /** * The results after updating all the fields on submission. */ -interface UpdateResult { +export interface UpdateResult { metadataUpdateRD: RemoteData, primaryUpdateRD: RemoteData, formatUpdateRD: RemoteData, From ce3e2c74440407fe9409491e0f26e5ef387f1109 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 6 Mar 2025 11:26:44 +0100 Subject: [PATCH 15/16] 115491: Export consts --- .../edit-bitstream-page.component.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 d941bb4b156..dd5eeb3a765 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 @@ -63,42 +63,42 @@ export interface UpdateResult { /** * Key prefix used to generate form messages */ -const KEY_PREFIX = 'bitstream.edit.form.'; +export const KEY_PREFIX = 'bitstream.edit.form.'; /** * Key suffix used to generate form labels */ -const LABEL_KEY_SUFFIX = '.label'; +export const LABEL_KEY_SUFFIX = '.label'; /** * Key suffix used to generate form labels */ -const HINT_KEY_SUFFIX = '.hint'; +export const HINT_KEY_SUFFIX = '.hint'; /** * Key prefix used to generate notification messages */ -const NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.'; +export const NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.'; /** * IIIF image width metadata key */ -const IMAGE_WIDTH_METADATA = 'iiif.image.width'; +export const IMAGE_WIDTH_METADATA = 'iiif.image.width'; /** * IIIF image height metadata key */ -const IMAGE_HEIGHT_METADATA = 'iiif.image.height'; +export const IMAGE_HEIGHT_METADATA = 'iiif.image.height'; /** * IIIF table of contents metadata key */ -const IIIF_TOC_METADATA = 'iiif.toc'; +export const IIIF_TOC_METADATA = 'iiif.toc'; /** * IIIF label metadata key */ -const IIIF_LABEL_METADATA = 'iiif.label'; +export const IIIF_LABEL_METADATA = 'iiif.label'; @Component({ selector: 'ds-edit-bitstream-page', From 6002f76ec6bdf9991b245ff6f111bc3925ae29e6 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 18 Mar 2025 15:27:45 +0100 Subject: [PATCH 16/16] 115491: Add doc comments & apply minor fixes --- .../edit-bitstream-page.component.ts | 21 +++++++++++++++---- .../shared/utils/observables-dictionary.ts | 4 ++-- 2 files changed, 19 insertions(+), 6 deletions(-) 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 dd5eeb3a765..d4eae095eb5 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 @@ -527,7 +527,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { this.updateFormatModel(); if (this.isIIIF) { - this.appendFormWIthIiifFields(); + this.appendFormWithIiifFields(); } } @@ -543,7 +543,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ updateForm() { const bitstream = this.bitstream; - const format = this.bitstreamFormat; this.formGroup.patchValue({ fileNamePrimaryContainer: { @@ -574,7 +573,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }); } - this.bitstreamFormat = format; + const format = this.bitstreamFormat; this.formGroup.patchValue({ formatContainer: { selectedFormat: format.id @@ -661,6 +660,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ); } + /** + * Collects all observables that update the different parts of the bitstream. + */ getUpdateObservables(updatedValues: any): ObservablesDictionary { return { metadataUpdateRD: this.updateBitstreamMetadataRD$(updatedValues), @@ -669,6 +671,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }; } + /** + * Creates and returns an observable that updates the bitstream metadata according to the data in the form. + */ updateBitstreamMetadataRD$(updatedValues: any): Observable> { const updatedBitstream = this.formToBitstream(updatedValues); @@ -776,6 +781,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { return updatedBitstream; } + /** + * Handle the update result by checking for errors. + * When there are no errors, the user is redirected to the edit-bitstreams page. + * When there are errors, a notification is shown. + */ handleUpdateResult(updateResult: UpdateResult) { let errorWhileSaving = false; @@ -846,7 +856,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { return isImage && isIIIFBundle && isEnabled; } - appendFormWIthIiifFields(): void { + /** + * Extend the form with IIIF fields + */ + appendFormWithIiifFields(): void { this.inputModels.push(this.iiifLabelModel); this.formModel.push(this.iiifLabelContainer); this.inputModels.push(this.iiifTocModel); diff --git a/src/app/shared/utils/observables-dictionary.ts b/src/app/shared/utils/observables-dictionary.ts index ac9cf907b9c..905b994633f 100644 --- a/src/app/shared/utils/observables-dictionary.ts +++ b/src/app/shared/utils/observables-dictionary.ts @@ -29,7 +29,7 @@ export type ObservablesDictionary = { } Now the input for the 'combineLatest' should be of type ObservablesDictionary. - In essence ObservablesDictionary creates a copy of the defined interface T, while making observables of all T's + In essence ObservablesDictionary creates a copy of the defined interface T, while making observables of all of T's fields. ObservablesDictionary also applies the additional constraint that all the keys of T must be strings, which is required for objects used in 'combineLatest'. @@ -55,7 +55,7 @@ export type ObservablesDictionary = { An example of this type in action can be found in the edit-bitstream-page component (as of writing this explainer). - The edit-bitstream-page has the following interface with that contains the required data: + The edit-bitstream-page has the following interface that contains the required data: interface DataObjects { bitstream: Bitstream,