From a9c5c20c5aa810d516eb28d87ee1f217336fe90a Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 16 Jun 2023 11:45:26 +0200 Subject: [PATCH 01/66] 102415: Provide all external sources of correct entity type in edit relationships ({item-page}/edit/relationship) --- .../dynamic-lookup-relation-modal.component.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index 69c2ac3b7f7..95acd0f41d6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -30,6 +30,10 @@ import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/ope import { followLink } from '../../../../utils/follow-link-config.model'; import { RelationshipType } from '../../../../../core/shared/item-relationships/relationship-type.model'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { FindListOptions } from '../../../../../core/data/request.models'; +import { RequestParam } from '../../../../../core/cache/models/request-param.model'; +import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators'; +import { PaginatedList } from '../../../../../core/data/paginated-list.model'; @Component({ selector: 'ds-dynamic-lookup-relation-modal', @@ -202,6 +206,19 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy ).pipe( getAllSucceededRemoteDataPayload() ); + } else { + const findListOptions = Object.assign({}, new FindListOptions(), { + elementsPerPage: 5, + currentPage: 1, + searchParams: [ + new RequestParam('entityType', this.relationshipOptions.relationshipType) + ] + }); + this.externalSourcesRD$ = this.externalSourceService.searchBy('findByEntityType', findListOptions, + true, true, followLink('entityTypes')) + .pipe(getFirstSucceededRemoteDataPayload(), map((r: PaginatedList) => { + return r.page; + })); } this.setTotals(); From cf9179d800dbaab74fd48b15bde4840cc1155f57 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Mon, 19 Jun 2023 16:29:15 +0200 Subject: [PATCH 02/66] 102415: Fix for import modal i18n labels --- .../dynamic-lookup-relation-modal.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index 95acd0f41d6..38b547d4a98 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -178,6 +178,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy if (!!this.currentItemIsLeftItem$) { this.currentItemIsLeftItem$.subscribe((isLeft) => { this.isLeft = isLeft; + this.label = this.relationshipType.leftwardType; }); } From 91b4d3dcd10cbd8248a41505eac96586707bac2b Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 10 Nov 2023 16:13:20 +0100 Subject: [PATCH 03/66] [DURACOM-204][#2622] Makes forgot-password link removable --- .../data/feature-authorization/feature-id.ts | 1 + .../password/log-in-password.component.html | 16 ++++++------ .../password/log-in-password.component.ts | 26 ++++++++++++++++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 8fef45a9532..bd1f65b72d8 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -34,4 +34,5 @@ export enum FeatureID { CanEditItem = 'canEditItem', CanRegisterDOI = 'canRegisterDOI', CanSubscribe = 'canSubscribeDso', + EPersonForgotPassword = 'epersonForgotPassword', } diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.html b/src/app/shared/log-in/methods/password/log-in-password.component.html index 60477d141de..52f28e7190e 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.html +++ b/src/app/shared/log-in/methods/password/log-in-password.component.html @@ -29,11 +29,11 @@ [disabled]="!form.valid"> {{"login.form.submit" | translate}} - + + + diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index 61323940192..1f96c4c0d8e 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -1,9 +1,9 @@ -import { map } from 'rxjs/operators'; +import { combineLatest, Observable, shareReplay } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { Component, Inject, OnInit } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { select, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../../../core/auth/auth.actions'; import { getAuthenticationError, getAuthenticationInfo, } from '../../../../core/auth/selectors'; @@ -73,6 +73,17 @@ export class LogInPasswordComponent implements OnInit { */ public canRegister$: Observable; + /** + * Whether or not the current user (or anonymous) is authorized to register an account + */ + canForgot$: Observable; + + /** + * Shows the divider only if contains at least one link to show + */ + canShowDivider$: Observable; + + constructor( @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, @Inject('isStandalonePage') public isStandalonePage: boolean, @@ -114,8 +125,15 @@ export class LogInPasswordComponent implements OnInit { return message; }) ); - - this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration); + + this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration).pipe(shareReplay(1)); + this.canForgot$ = this.authorizationService.isAuthorized(FeatureID.EPersonForgotPassword).pipe(shareReplay(1)); + this.canShowDivider$ = + combineLatest([this.canRegister$, this.canForgot$]) + .pipe( + map(([canRegister, canForgot]) => canRegister || canForgot), + filter(Boolean) + ); } getRegisterRoute() { From 2429c3660b8ae370a8da945a9c3483b185b201e9 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 10 Nov 2023 16:49:06 +0100 Subject: [PATCH 04/66] [DURACOM-204][#2622] Makes forgot-password link removable --- .../log-in/methods/password/log-in-password.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index 1f96c4c0d8e..008eb11eef1 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -64,7 +64,7 @@ export class LogInPasswordComponent implements OnInit { /** * The authentication form. - * @type {FormGroup} + * @type {UntypedFormGroup} */ public form: UntypedFormGroup; @@ -125,7 +125,7 @@ export class LogInPasswordComponent implements OnInit { return message; }) ); - + this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration).pipe(shareReplay(1)); this.canForgot$ = this.authorizationService.isAuthorized(FeatureID.EPersonForgotPassword).pipe(shareReplay(1)); this.canShowDivider$ = From 98241d8925cbc394c78feb124cdea476c67da4be Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 21:54:32 +0300 Subject: [PATCH 05/66] src/assets/i18n: change "controller" to "reviewer" Reviewer is a less obscure term for what this actually is in most cases. --- src/assets/i18n/ar.json5 | 4 ++-- src/assets/i18n/bn.json5 | 2 +- src/assets/i18n/ca.json5 | 2 +- src/assets/i18n/cs.json5 | 4 ++-- src/assets/i18n/de.json5 | 2 +- src/assets/i18n/en.json5 | 4 ++-- src/assets/i18n/es.json5 | 2 +- src/assets/i18n/fi.json5 | 2 +- src/assets/i18n/fr.json5 | 2 +- src/assets/i18n/gd.json5 | 2 +- src/assets/i18n/hu.json5 | 2 +- src/assets/i18n/it.json5 | 2 +- src/assets/i18n/ja.json5 | 4 ++-- src/assets/i18n/kk.json5 | 2 +- src/assets/i18n/lv.json5 | 2 +- src/assets/i18n/nl.json5 | 2 +- src/assets/i18n/pt-BR.json5 | 2 +- src/assets/i18n/pt-PT.json5 | 2 +- src/assets/i18n/sv.json5 | 2 +- src/assets/i18n/sw.json5 | 4 ++-- src/assets/i18n/tr.json5 | 2 +- src/assets/i18n/uk.json5 | 2 +- 22 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/assets/i18n/ar.json5 b/src/assets/i18n/ar.json5 index 3069104dd9a..617d2a93ded 100644 --- a/src/assets/i18n/ar.json5 +++ b/src/assets/i18n/ar.json5 @@ -4284,9 +4284,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5 index c70cc6f4595..c9c5ba26402 100644 --- a/src/assets/i18n/bn.json5 +++ b/src/assets/i18n/bn.json5 @@ -3886,7 +3886,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "বৈধতা", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "নিয়ামক জন্য অপেক্ষা করছে", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/ca.json5 b/src/assets/i18n/ca.json5 index ad8fe49424e..db624f2c3f7 100644 --- a/src/assets/i18n/ca.json5 +++ b/src/assets/i18n/ca.json5 @@ -4196,7 +4196,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validació", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Esperant el controlador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 7f9583a50eb..9816571e057 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -4195,9 +4195,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c185a13432b..f77d1a21fd8 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3491,7 +3491,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validierung", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Warten auf die Überprüfung", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 643a3ce0d18..78d034c4170 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3042,7 +3042,7 @@ "mydspace.status.mydspaceValidation": "Validation", - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWorkflow": "Workflow", @@ -3864,7 +3864,7 @@ "search.filters.namedresourcetype.Validation": "Validation", - "search.filters.namedresourcetype.Waiting for Controller": "Waiting for Controller", + "search.filters.namedresourcetype.Waiting for Controller": "Waiting for reviewer", "search.filters.namedresourcetype.Workflow": "Workflow", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 0d5b27473cf..dc4e3afcc70 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -4524,7 +4524,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validación", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Esperando al controlador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index ede41ffb0c2..914dfa8f456 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -4479,7 +4479,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validointi", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Odottaa tarkastajaa", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 699ca5cc27b..80566e589ea 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -3828,7 +3828,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "En cours de validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "En attente d'assignation", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5 index 55a53bc6f1b..929ab87d108 100644 --- a/src/assets/i18n/gd.json5 +++ b/src/assets/i18n/gd.json5 @@ -3873,7 +3873,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Dearbhadh", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "A' feitheamh riaghladair", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5 index d186f6435a7..afbfa25a136 100644 --- a/src/assets/i18n/hu.json5 +++ b/src/assets/i18n/hu.json5 @@ -4989,7 +4989,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Érvényesítés", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Várakozás a kontrollerre", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 7f410ce0b17..ad2478ae615 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -4570,7 +4570,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Convalida", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "In attesa del controllo", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/ja.json5 b/src/assets/i18n/ja.json5 index da2385fd62f..03323eb7ef4 100644 --- a/src/assets/i18n/ja.json5 +++ b/src/assets/i18n/ja.json5 @@ -4284,9 +4284,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5 index d23dc23c475..dbaa8078e2a 100644 --- a/src/assets/i18n/kk.json5 +++ b/src/assets/i18n/kk.json5 @@ -4145,7 +4145,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Валидация", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Контроллерді күтуде", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5 index 81e2383a1f8..3ceaac7fea9 100644 --- a/src/assets/i18n/lv.json5 +++ b/src/assets/i18n/lv.json5 @@ -3498,7 +3498,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validācija", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Gaida kontrolieri", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/nl.json5 b/src/assets/i18n/nl.json5 index 280a87b96fe..fc52543c383 100644 --- a/src/assets/i18n/nl.json5 +++ b/src/assets/i18n/nl.json5 @@ -3770,7 +3770,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validatie", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Wachten op controlleur", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index ce35f1ec055..5061c5a0e99 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -4533,7 +4533,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validação", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Esperando pelo controlador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5 index faa027705e0..328cb208102 100644 --- a/src/assets/i18n/pt-PT.json5 +++ b/src/assets/i18n/pt-PT.json5 @@ -4478,7 +4478,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Em validação", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Aguarda validador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index 4e3576ccfc0..d2fe72536c7 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -3940,7 +3940,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validering", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Väntar på kontrollant", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/sw.json5 b/src/assets/i18n/sw.json5 index a470ee4b587..d2d663cdc53 100644 --- a/src/assets/i18n/sw.json5 +++ b/src/assets/i18n/sw.json5 @@ -4284,9 +4284,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5 index 153eaa1281a..75bc5c9a362 100644 --- a/src/assets/i18n/tr.json5 +++ b/src/assets/i18n/tr.json5 @@ -3263,7 +3263,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Doğrulama", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Kontrolör bekleniyor", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/uk.json5 b/src/assets/i18n/uk.json5 index 7df55fa2369..fae770b6bd3 100644 --- a/src/assets/i18n/uk.json5 +++ b/src/assets/i18n/uk.json5 @@ -3389,7 +3389,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Перевірка", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Чекаємо контролера", // "mydspace.status.mydspaceWorkflow": "Workflow", From f78f4b45fcb82654c2cca08cccb254f414a3244f Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 22:02:19 +0300 Subject: [PATCH 06/66] src/assets/i18n/en.json5: minor updates for consistency We should be capitalizing acronyms and project-specific nouns like MyDSpace consistently in our interface. --- src/assets/i18n/en.json5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 78d034c4170..b3e1759c1b5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -590,9 +590,9 @@ "admin.metadata-import.page.error.addFile": "Select file first!", - "admin.metadata-import.page.error.addFileUrl": "Insert file url first!", + "admin.metadata-import.page.error.addFileUrl": "Insert file URL first!", - "admin.batch-import.page.error.addFile": "Select Zip file first!", + "admin.batch-import.page.error.addFile": "Select ZIP file first!", "admin.metadata-import.page.toggle.upload": "Upload", @@ -3028,9 +3028,9 @@ "mydspace.results.no-title": "No title", - "mydspace.results.no-uri": "No Uri", + "mydspace.results.no-uri": "No URI", - "mydspace.search-form.placeholder": "Search in mydspace...", + "mydspace.search-form.placeholder": "Search in MyDSpace...", "mydspace.show.workflow": "Workflow tasks", From d6c46847c2340c2e2755a81b0909e30a25052ea5 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 22:03:11 +0300 Subject: [PATCH 07/66] src/assets/i18n/en.json5: minor changes for consistency Fix some random capitalizations and strange wording. --- src/assets/i18n/en.json5 | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b3e1759c1b5..7bf16c4ee8b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4,15 +4,15 @@ "401.link.home-page": "Take me to the home page", - "401.unauthorized": "unauthorized", + "401.unauthorized": "Unauthorized", "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", "403.link.home-page": "Take me to the home page", - "403.forbidden": "forbidden", + "403.forbidden": "Forbidden", - "500.page-internal-server-error": "Service Unavailable", + "500.page-internal-server-error": "Service unavailable", "500.help": "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.", @@ -22,15 +22,15 @@ "404.link.home-page": "Take me to the home page", - "404.page-not-found": "page not found", + "404.page-not-found": "Page not found", - "error-page.description.401": "unauthorized", + "error-page.description.401": "Unauthorized", - "error-page.description.403": "forbidden", + "error-page.description.403": "Forbidden", - "error-page.description.500": "Service Unavailable", + "error-page.description.500": "Service unavailable", - "error-page.description.404": "page not found", + "error-page.description.404": "Page not found", "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", @@ -58,7 +58,7 @@ "admin.registries.bitstream-formats.create.failure.head": "Failure", - "admin.registries.bitstream-formats.create.head": "Create Bitstream format", + "admin.registries.bitstream-formats.create.head": "Create bitstream format", "admin.registries.bitstream-formats.create.new": "Add a new bitstream format", @@ -300,7 +300,7 @@ "admin.access-control.epeople.form.email": "E-mail", - "admin.access-control.epeople.form.emailHint": "Must be valid e-mail address", + "admin.access-control.epeople.form.emailHint": "Must be a valid e-mail address", "admin.access-control.epeople.form.canLogIn": "Can log in", @@ -1192,9 +1192,9 @@ "community.edit.logo.label": "Community logo", - "community.edit.logo.notifications.add.error": "Uploading Community logo failed. Please verify the content before retrying.", + "community.edit.logo.notifications.add.error": "Uploading community logo failed. Please verify the content before retrying.", - "community.edit.logo.notifications.add.success": "Upload Community logo successful.", + "community.edit.logo.notifications.add.success": "Upload community logo successful.", "community.edit.logo.notifications.delete.success.title": "Logo deleted", @@ -1202,13 +1202,13 @@ "community.edit.logo.notifications.delete.error.title": "Error deleting logo", - "community.edit.logo.upload": "Drop a Community Logo to upload", + "community.edit.logo.upload": "Drop a community logo to upload", "community.edit.notifications.success": "Successfully edited the Community", "community.edit.notifications.unauthorized": "You do not have privileges to make this change", - "community.edit.notifications.error": "An error occured while editing the Community", + "community.edit.notifications.error": "An error occured while editing the community", "community.edit.return": "Back", @@ -1602,9 +1602,9 @@ "error.validation.required": "This field is required", - "error.validation.NotValidEmail": "This E-mail is not a valid email", + "error.validation.NotValidEmail": "This is not a valid e-mail", - "error.validation.emailTaken": "This E-mail is already taken", + "error.validation.emailTaken": "This e-mail is already taken", "error.validation.groupExists": "This group already exists", @@ -3034,7 +3034,7 @@ "mydspace.show.workflow": "Workflow tasks", - "mydspace.show.workspace": "Your Submissions", + "mydspace.show.workspace": "Your submissions", "mydspace.show.supervisedWorkspace": "Supervised items", @@ -3070,7 +3070,7 @@ "nav.login": "Log In", - "nav.user-profile-menu-and-logout": "User profile menu and Log Out", + "nav.user-profile-menu-and-logout": "User profile menu and log out", "nav.logout": "Log Out", From 2327513dd02db70c8a3e75388ef91028b341ba0d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 15:12:22 +0200 Subject: [PATCH 08/66] Created AbstractComponentLoaderComponent to load components dynamically --- .../abstract-component-loader.component.html | 1 + .../abstract-component-loader.component.ts | 113 ++++++++++++++++++ .../dynamic-component-loader.directive.ts | 16 +++ src/app/shared/shared.module.ts | 2 + 4 files changed, 132 insertions(+) create mode 100644 src/app/shared/abstract-component-loader/abstract-component-loader.component.html create mode 100644 src/app/shared/abstract-component-loader/abstract-component-loader.component.ts create mode 100644 src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.html b/src/app/shared/abstract-component-loader/abstract-component-loader.component.html new file mode 100644 index 00000000000..2035dbadd08 --- /dev/null +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.html @@ -0,0 +1 @@ + diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts new file mode 100644 index 00000000000..6f934f5b4bf --- /dev/null +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -0,0 +1,113 @@ +import { Component, ComponentRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; +import { Context } from '../../core/shared/context.model'; +import { ThemeService } from '../theme-support/theme.service'; +import { GenericConstructor } from '../../core/shared/generic-constructor'; +import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { Subscription } from 'rxjs'; +import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive'; + +@Component({ + selector: 'ds-abstract-component-loader', + templateUrl: './abstract-component-loader.component.html', +}) +export abstract class AbstractComponentLoaderComponent implements OnInit, OnChanges, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * Directive to determine where the dynamic child component is located + */ + @ViewChild(DynamicComponentLoaderDirective, { static: true }) componentDirective: DynamicComponentLoaderDirective; + + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + */ + protected subs: Subscription[] = []; + + protected inAndOutputNames: (keyof this)[] = [ + 'context', + ]; + + constructor( + protected themeService: ThemeService, + ) { + } + + /** + * Set up the dynamic child component + */ + ngOnInit(): void { + this.instantiateComponent(); + } + + /** + * Whenever the inputs change, update the inputs of the dynamic component + */ + ngOnChanges(changes: SimpleChanges): void { + if (hasNoValue(this.compRef)) { + // sometimes the component has not been initialized yet, so it first needs to be initialized + // before being called again + this.instantiateComponent(changes); + } else { + // if an input or output has changed + if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + this.connectInputsAndOutputs(); + if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { + (this.compRef.instance as any).ngOnChanges(changes); + } + } + } + } + + ngOnDestroy(): void { + this.subs + .filter((subscription: Subscription) => hasValue(subscription)) + .forEach((subscription: Subscription) => subscription.unsubscribe()); + } + + public instantiateComponent(changes?: SimpleChanges): void { + const component: GenericConstructor = this.getComponent(); + + const viewContainerRef: ViewContainerRef = this.componentDirective.viewContainerRef; + viewContainerRef.clear(); + + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + }, + ); + + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); + } + } + + /** + * Fetch the component depending on the item's entity type, metadata representation type and context + */ + public abstract getComponent(): GenericConstructor; + + /** + * Connect the in and outputs of this component to the dynamic component, + * to ensure they're in sync + */ + protected connectInputsAndOutputs(): void { + if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { + this.compRef.instance[name] = this[name]; + }); + } + } + +} diff --git a/src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts b/src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts new file mode 100644 index 00000000000..8c77df1cdb8 --- /dev/null +++ b/src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts @@ -0,0 +1,16 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +/** + * Directive used as a hook to know where to inject the dynamic loaded component + */ +@Directive({ + selector: '[dsDynamicComponentLoader]' +}) +export class DynamicComponentLoaderDirective { + + constructor( + public viewContainerRef: ViewContainerRef, + ) { + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 0f7871f7f9b..2f9d3317fb5 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -284,6 +284,7 @@ import { } from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component'; import { NgxPaginationModule } from 'ngx-pagination'; +import { DynamicComponentLoaderDirective } from './abstract-component-loader/dynamic-component-loader.directive'; const MODULES = [ CommonModule, @@ -491,6 +492,7 @@ const DIRECTIVES = [ MetadataFieldValidator, HoverClassDirective, ContextHelpDirective, + DynamicComponentLoaderDirective, ]; @NgModule({ From fb7afaddd047c73ebf4cc97db70236e56f1a8f96 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 15:45:59 +0200 Subject: [PATCH 09/66] Make ListableObjectComponentLoaderComponent extend AbstractComponentLoaderComponent --- ...-search-result-grid-element.component.html | 2 +- ...in-search-result-grid-element.component.ts | 6 +- ...admin-workflow-grid-element.component.html | 2 +- ...in-workflow-grid-element.component.spec.ts | 12 +- ...t-admin-workflow-grid-element.component.ts | 6 +- ...admin-workflow-grid-element.component.html | 2 +- ...in-workflow-grid-element.component.spec.ts | 12 +- ...t-admin-workflow-grid-element.component.ts | 8 +- ...ble-object-component-loader.component.html | 1 - ...-object-component-loader.component.spec.ts | 22 +-- ...table-object-component-loader.component.ts | 135 +++--------------- .../listable-object.directive.ts | 11 -- src/app/shared/shared.module.ts | 2 - 13 files changed, 59 insertions(+), 162 deletions(-) delete mode 100644 src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html delete mode 100644 src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html index c4b737849b6..639c47f7f8c 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index dab6694f368..b6271b5ad5d 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -11,7 +11,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; -import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @@ -25,7 +25,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service * The component for displaying a list element for an item search result on the admin search page */ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective; @ViewChild('badges', { static: true }) badges: ElementRef; @ViewChild('buttons', { static: true }) buttons: ElementRef; @@ -46,7 +46,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE super.ngOnInit(); const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent( diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html index 87bae0c2613..c5c2a5331ad 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts index 8035c53547e..b02fa476ea2 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts @@ -18,8 +18,8 @@ import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; import { - ListableObjectDirective -} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; + DynamicComponentLoaderDirective +} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; @@ -38,7 +38,7 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => { let itemRD$; let linkService; let object; - let themeService; + let themeService: ThemeService; function init() { itemRD$ = createSuccessfulRemoteDataObject$(new Item()); @@ -55,7 +55,11 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => { init(); TestBed.configureTestingModule( { - declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective], + declarations: [ + WorkflowItemSearchResultAdminWorkflowGridElementComponent, + ItemGridElementComponent, + DynamicComponentLoaderDirective, + ], imports: [ NoopAnimationsModule, TranslateModule.forRoot(), diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts index fd9d21e227d..d4a69b829d4 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts @@ -10,7 +10,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; -import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; import { Observable } from 'rxjs'; import { LinkService } from '../../../../../core/cache/builders/link.service'; @@ -38,7 +38,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S /** * Directive used to render the dynamic component in */ - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective; /** * The html child that contains the badges html @@ -77,7 +77,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S this.item$.pipe(take(1)).subscribe((item: Item) => { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent( diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html index 767ad79786f..f78a0a3ca43 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts index b9e752c1047..d023e57709f 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts @@ -20,8 +20,8 @@ import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; import { - ListableObjectDirective -} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; + DynamicComponentLoaderDirective +} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; @@ -45,7 +45,7 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => { let itemRD$; let linkService; let object; - let themeService; + let themeService: ThemeService; let supervisionOrderDataService; function init() { @@ -67,7 +67,11 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => { init(); TestBed.configureTestingModule( { - declarations: [WorkspaceItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective], + declarations: [ + WorkspaceItemSearchResultAdminWorkflowGridElementComponent, + ItemGridElementComponent, + DynamicComponentLoaderDirective, + ], imports: [ NoopAnimationsModule, TranslateModule.forRoot(), diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts index d6f39e79feb..f74d8b3e5cb 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts @@ -16,9 +16,7 @@ import { import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; -import { - ListableObjectDirective -} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; import { LinkService } from '../../../../../core/cache/builders/link.service'; import { followLink } from '../../../../../shared/utils/follow-link-config.model'; @@ -67,7 +65,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends /** * Directive used to render the dynamic component in */ - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective; /** * The html child that contains the badges html @@ -102,7 +100,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends this.item$.pipe(take(1)).subscribe((item: Item) => { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent( diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html deleted file mode 100644 index 58561f0277d..00000000000 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index e893fe807b7..7b9d010e398 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -8,7 +8,7 @@ import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; -import { ListableObjectDirective } from './listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { provideMockStore } from '@ngrx/store/testing'; @@ -36,7 +36,7 @@ describe('ListableObjectComponentLoaderComponent', () => { }); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective], + declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, DynamicComponentLoaderDirective], schemas: [NO_ERRORS_SCHEMA], providers: [ provideMockStore({}), @@ -65,7 +65,7 @@ describe('ListableObjectComponentLoaderComponent', () => { describe('When the component is rendered', () => { it('should call the getListableObjectComponent function with the right types, view mode and context', () => { - expect(comp.getComponent).toHaveBeenCalledWith([testType], testViewMode, testContext); + expect(comp.getComponent).toHaveBeenCalled(); }); it('should connectInputsAndOutputs of loaded component', () => { @@ -78,29 +78,29 @@ describe('ListableObjectComponentLoaderComponent', () => { let reloadedObject: any; beforeEach(() => { - spyOn((comp as any), 'instantiateComponent').and.returnValue(null); - spyOn((comp as any).contentChange, 'emit').and.returnValue(null); + spyOn(comp, 'instantiateComponent').and.returnValue(null); + spyOn(comp.contentChange, 'emit').and.returnValue(null); listableComponent = fixture.debugElement.query(By.css('ds-item-list-element')).componentInstance; reloadedObject = 'object'; }); it('should re-instantiate the listable component', fakeAsync(() => { - expect((comp as any).instantiateComponent).not.toHaveBeenCalled(); + expect(comp.instantiateComponent).not.toHaveBeenCalled(); - (listableComponent as any).reloadedObject.emit(reloadedObject); + listableComponent.reloadedObject.emit(reloadedObject); tick(200); - expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject, undefined); + expect(comp.instantiateComponent).toHaveBeenCalledWith(undefined); })); it('should re-emit it as a contentChange', fakeAsync(() => { - expect((comp as any).contentChange.emit).not.toHaveBeenCalled(); + expect(comp.contentChange.emit).not.toHaveBeenCalled(); - (listableComponent as any).reloadedObject.emit(reloadedObject); + listableComponent.reloadedObject.emit(reloadedObject); tick(200); - expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject); + expect(comp.contentChange.emit).toHaveBeenCalledWith(reloadedObject); })); }); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 7a3cc42bf5a..f62ee44a1c2 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,40 +1,26 @@ -import { - ChangeDetectorRef, - Component, - ComponentRef, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - SimpleChanges, - ViewChild -} from '@angular/core'; - -import { Subscription, combineLatest, of as observableOf, Observable } from 'rxjs'; +import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; +import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { take } from 'rxjs/operators'; - import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { Context } from '../../../../core/shared/context.model'; +import { Context } from 'src/app/core/shared/context.model'; import { getListableObjectComponent } from './listable-object.decorator'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; -import { ListableObjectDirective } from './listable-object.directive'; import { CollectionElementLinkType } from '../../collection-element-link.type'; -import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; -import { ThemeService } from '../../../theme-support/theme.service'; +import { AbstractComponentLoaderComponent } from '../../../abstract-component-loader/abstract-component-loader.component'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; @Component({ selector: 'ds-listable-object-component-loader', styleUrls: ['./listable-object-component-loader.component.scss'], - templateUrl: './listable-object-component-loader.component.html' + templateUrl: '../../../abstract-component-loader/abstract-component-loader.component.html', }) /** * Component for determining what component to use depending on the item's entity type (dspace.entity.type) */ -export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges, OnDestroy { +export class ListableObjectComponentLoaderComponent extends AbstractComponentLoaderComponent { + /** * The item or metadata to determine the component for */ @@ -80,99 +66,36 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ @Input() value: string; - /** - * Directive hook used to place the dynamic child component - */ - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; - /** * Emit when the listable object has been reloaded. */ @Output() contentChange = new EventEmitter(); - /** - * Array to track all subscriptions and unsubscribe them onDestroy - * @type {Array} - */ - protected subs: Subscription[] = []; - - /** - * The reference to the dynamic component - */ - protected compRef: ComponentRef; - /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: string[] = [ + protected inAndOutputNames: (keyof this)[] = [ + ...this.inAndOutputNames, 'object', 'index', 'linkType', 'listID', 'showLabel', 'showThumbnails', - 'context', 'viewMode', 'value', - 'hideBadges', 'contentChange', ]; - constructor(private cdr: ChangeDetectorRef, private themeService: ThemeService) { - } - - /** - * Setup the dynamic child component - */ - ngOnInit(): void { - this.instantiateComponent(this.object); + constructor( + protected themeService: ThemeService, + protected cdr: ChangeDetectorRef, + ) { + super(themeService); } - /** - * Whenever the inputs change, update the inputs of the dynamic component - */ - ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(this.object, changes); - } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } - } - } - } - - ngOnDestroy() { - this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); - } - - private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void { - - const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context); - - const viewContainerRef = this.listableObjectDirective.viewContainerRef; - viewContainerRef.clear(); - - this.compRef = viewContainerRef.createComponent( - component, { - index: 0, - injector: undefined - } - ); - - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); - } - + public instantiateComponent(changes?: SimpleChanges): void { + super.instantiateComponent(changes); if ((this.compRef.instance as any).reloadedObject) { combineLatest([ observableOf(changes), @@ -181,7 +104,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges if (reloadedObject) { this.compRef.destroy(); this.object = reloadedObject; - this.instantiateComponent(reloadedObject, simpleChanges); + this.instantiateComponent(simpleChanges); this.cdr.detectChanges(); this.contentChange.emit(reloadedObject); } @@ -189,26 +112,8 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges } } - /** - * Fetch the component depending on the item's entity type, view mode and context - * @returns {GenericConstructor} - */ - getComponent(renderTypes: (string | GenericConstructor)[], - viewMode: ViewMode, - context: Context): GenericConstructor { - return getListableObjectComponent(renderTypes, viewMode, context, this.themeService.getThemeName()); - } - - /** - * Connect the in and outputs of this component to the dynamic component, - * to ensure they're in sync - */ - protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { - this.compRef.instance[name] = this[name]; - }); - } + public getComponent(): GenericConstructor { + return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context, this.themeService.getThemeName()); } } diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts b/src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts deleted file mode 100644 index 93c06961f4c..00000000000 --- a/src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsListableObject]', -}) -/** - * Directive used as a hook to know where to inject the dynamic listable object component - */ -export class ListableObjectDirective { - constructor(public viewContainerRef: ViewContainerRef) { } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2f9d3317fb5..f185847596b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -177,7 +177,6 @@ import { import { ItemSearchResultListElementComponent } from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; -import { ListableObjectDirective } from './object-collection/shared/listable-object/listable-object.directive'; import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; @@ -484,7 +483,6 @@ const DIRECTIVES = [ AutoFocusDirective, RoleDirective, MetadataRepresentationDirective, - ListableObjectDirective, ClaimedTaskActionsDirective, FileValueAccessorDirective, FileValidator, From fe60adb47f47f304433599e97df9f6b50320ab70 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 15:58:31 +0200 Subject: [PATCH 10/66] Make ClaimedTaskActionsLoaderComponent extend AbstractComponentLoaderComponent --- ...claimed-task-actions-loader.component.html | 1 - ...imed-task-actions-loader.component.spec.ts | 22 +++-- .../claimed-task-actions-loader.component.ts | 98 +++---------------- .../claimed-task-actions.directive.ts | 11 --- src/app/shared/shared.module.ts | 2 - 5 files changed, 27 insertions(+), 107 deletions(-) delete mode 100644 src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html delete mode 100644 src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html deleted file mode 100644 index 364443c47f6..00000000000 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts index 95e31f55239..e48983d449f 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts @@ -1,7 +1,7 @@ import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; +import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskActionsEditMetadataComponent } from '../edit-metadata/claimed-task-actions-edit-metadata.component'; @@ -17,6 +17,8 @@ import { getMockSearchService } from '../../../mocks/search-service.mock'; import { getMockRequestService } from '../../../mocks/request.service.mock'; import { Item } from '../../../../core/shared/item.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; +import { getMockThemeService } from '../../../mocks/theme-service.mock'; const searchService = getMockSearchService(); @@ -25,6 +27,7 @@ const requestService = getMockRequestService(); describe('ClaimedTaskActionsLoaderComponent', () => { let comp: ClaimedTaskActionsLoaderComponent; let fixture: ComponentFixture; + let themeService: ThemeService; const option = 'test_option'; const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); @@ -61,9 +64,15 @@ describe('ClaimedTaskActionsLoaderComponent', () => { const workflowitem = Object.assign(new WorkflowItem(), { id: '333' }); beforeEach(waitForAsync(() => { + themeService = getMockThemeService('dspace'); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [ClaimedTaskActionsLoaderComponent, ClaimedTaskActionsEditMetadataComponent, ClaimedTaskActionsDirective], + declarations: [ + ClaimedTaskActionsLoaderComponent, + ClaimedTaskActionsEditMetadataComponent, + DynamicComponentLoaderDirective, + ], schemas: [NO_ERRORS_SCHEMA], providers: [ { provide: ClaimedTaskDataService, useValue: {} }, @@ -72,7 +81,8 @@ describe('ClaimedTaskActionsLoaderComponent', () => { { provide: Router, useValue: new RouterStub() }, { provide: SearchService, useValue: searchService }, { provide: RequestService, useValue: requestService }, - { provide: PoolTaskDataService, useValue: {} } + { provide: PoolTaskDataService, useValue: {} }, + { provide: ThemeService, useValue: themeService }, ] }).overrideComponent(ClaimedTaskActionsLoaderComponent, { set: { @@ -89,14 +99,14 @@ describe('ClaimedTaskActionsLoaderComponent', () => { comp.object = object; comp.option = option; comp.workflowitem = workflowitem; - spyOn(comp, 'getComponentByWorkflowTaskOption').and.returnValue(ClaimedTaskActionsEditMetadataComponent); + spyOn(comp, 'getComponent').and.returnValue(ClaimedTaskActionsEditMetadataComponent); fixture.detectChanges(); })); describe('When the component is rendered', () => { - it('should call the getComponentByWorkflowTaskOption function with the right option', () => { - expect(comp.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(option); + it('should call the getComponent function', () => { + expect(comp.getComponent).toHaveBeenCalled(); }); }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index c0dc1cad02b..75b392fe516 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,33 +1,22 @@ -import { - Component, - ComponentFactoryResolver, - EventEmitter, - Input, - OnInit, - Output, - ViewChild, - OnChanges, - SimpleChanges, - ComponentRef, -} from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; -import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; -import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; import { Item } from '../../../../core/shared/item.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; +import { AbstractComponentLoaderComponent } from '../../../abstract-component-loader/abstract-component-loader.component'; +import { GenericConstructor } from '../../../../core/shared/generic-constructor'; @Component({ selector: 'ds-claimed-task-actions-loader', - templateUrl: './claimed-task-actions-loader.component.html' + templateUrl: '../../../abstract-component-loader/abstract-component-loader.component.html', }) /** * Component for loading a ClaimedTaskAction component depending on the "option" input * Passes on the ClaimedTask to the component and subscribes to the processCompleted output */ -export class ClaimedTaskActionsLoaderComponent implements OnInit, OnChanges { +export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit, OnChanges { /** * The item object that belonging to the ClaimedTask object */ @@ -54,85 +43,20 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnChanges { */ @Output() processCompleted = new EventEmitter(); - /** - * Directive to determine where the dynamic child component is located - */ - @ViewChild(ClaimedTaskActionsDirective, {static: true}) claimedTaskActionsDirective: ClaimedTaskActionsDirective; - - /** - * The reference to the dynamic component - */ - protected compRef: ComponentRef; - /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof ClaimedTaskActionsAbstractComponent & keyof this)[] = [ + protected inAndOutputNames: (keyof this)[] = [ + ...this.inAndOutputNames, + 'item', 'object', 'option', + 'workflowitem', 'processCompleted', ]; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { - } - - /** - * Fetch, create and initialize the relevant component - */ - ngOnInit(): void { - this.instantiateComponent(); - } - - /** - * Whenever the inputs change, update the inputs of the dynamic component - */ - ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(changes); - } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } - } - } - } - - private instantiateComponent(changes?: SimpleChanges): void { - const comp = this.getComponentByWorkflowTaskOption(this.option); - if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; - viewContainerRef.clear(); - - this.compRef = viewContainerRef.createComponent(componentFactory); - - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); - } - } - } - - getComponentByWorkflowTaskOption(option: string) { - return getComponentByWorkflowTaskOption(option); + public getComponent(): GenericConstructor { + return getComponentByWorkflowTaskOption(this.option); } - /** - * Connect the in and outputs of this component to the dynamic component, - * to ensure they're in sync - */ - protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { - this.compRef.instance[name] = this[name]; - }); - } - } } diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts deleted file mode 100644 index a4a55b541b4..00000000000 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsClaimedTaskActions]', -}) -/** - * Directive used as a hook to know where to inject the dynamic Claimed Task Actions component - */ -export class ClaimedTaskActionsDirective { - constructor(public viewContainerRef: ViewContainerRef) { } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index f185847596b..c00b09e20fc 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -197,7 +197,6 @@ import { FileValueAccessorDirective } from './utils/file-value-accessor.directiv import { ModifyItemOverviewComponent } from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component'; -import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive'; import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component'; import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive'; import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component'; @@ -483,7 +482,6 @@ const DIRECTIVES = [ AutoFocusDirective, RoleDirective, MetadataRepresentationDirective, - ClaimedTaskActionsDirective, FileValueAccessorDirective, FileValidator, NgForTrackByIdDirective, From 774784a9b949943c8bdd87ead3dff280acac115e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 16:03:07 +0200 Subject: [PATCH 11/66] Make AdvancedWorkflowActionsLoaderComponent extend AbstractComponentLoaderComponent --- ...ced-workflow-actions-loader.component.html | 1 - ...ced-workflow-actions-loader.component.scss | 0 ...-workflow-actions-loader.component.spec.ts | 17 ++++++--- ...anced-workflow-actions-loader.component.ts | 38 ++++++++----------- .../advanced-workflow-actions.directive.ts | 16 -------- .../workflowitems-edit-page.module.ts | 4 -- 6 files changed, 27 insertions(+), 49 deletions(-) delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html deleted file mode 100644 index 0904d0fcde5..00000000000 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts index 2c12b07589f..3188d00891e 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts @@ -3,12 +3,14 @@ import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-acti import { Router } from '@angular/router'; import { RouterStub } from '../../../shared/testing/router.stub'; import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; +import { DynamicComponentLoaderDirective } from '../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { rendersAdvancedWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; import { By } from '@angular/platform-browser'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; +import { getMockThemeService } from 'src/app/shared/mocks/theme-service.mock'; const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction'; @@ -17,17 +19,20 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { let fixture: ComponentFixture; let router: RouterStub; + let themeService: ThemeService; beforeEach(async () => { router = new RouterStub(); + themeService = getMockThemeService(); await TestBed.configureTestingModule({ declarations: [ - AdvancedWorkflowActionsDirective, + DynamicComponentLoaderDirective, AdvancedWorkflowActionsLoaderComponent, ], providers: [ { provide: Router, useValue: router }, + { provide: ThemeService, useValue: themeService }, ], }).overrideComponent(AdvancedWorkflowActionsLoaderComponent, { set: { @@ -50,24 +55,24 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { describe('When the component is rendered', () => { it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => { - spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent); + spyOn(component, 'getComponent').and.returnValue(AdvancedWorkflowActionTestComponent); component.ngOnInit(); fixture.detectChanges(); - expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST); + expect(component.getComponent).toHaveBeenCalled(); expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull(); expect(router.navigate).not.toHaveBeenCalled(); }); it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => { - spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined); + spyOn(component, 'getComponent').and.returnValue(undefined); component.type = 'nonexistingaction'; component.ngOnInit(); fixture.detectChanges(); - expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction'); + expect(component.getComponent).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index 32f14c015de..10cae7ec7df 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -1,21 +1,22 @@ -import { Component, Input, ViewChild, ComponentFactoryResolver, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { hasValue } from '../../../shared/empty.util'; import { getAdvancedComponentByWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; -import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; import { Router } from '@angular/router'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; +import { GenericConstructor } from '../../../core/shared/generic-constructor'; +import { AbstractComponentLoaderComponent } from 'src/app/shared/abstract-component-loader/abstract-component-loader.component'; +import { ThemeService } from '../../../shared/theme-support/theme.service'; /** * Component for loading a {@link AdvancedWorkflowActionComponent} depending on the "{@link type}" input */ @Component({ selector: 'ds-advanced-workflow-actions-loader', - templateUrl: './advanced-workflow-actions-loader.component.html', - styleUrls: ['./advanced-workflow-actions-loader.component.scss'], + templateUrl: '../../../shared/abstract-component-loader/abstract-component-loader.component.html', }) -export class AdvancedWorkflowActionsLoaderComponent implements OnInit { +export class AdvancedWorkflowActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit { /** * The name of the type to render @@ -23,35 +24,28 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { */ @Input() type: string; - /** - * Directive to determine where the dynamic child component is located - */ - @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective; + protected inAndOutputNames: (keyof this)[] = [ + ...this.inAndOutputNames, + 'type', + ]; constructor( - private componentFactoryResolver: ComponentFactoryResolver, + protected themeService: ThemeService, private router: Router, ) { + super(themeService); } - /** - * Fetch, create and initialize the relevant component - */ ngOnInit(): void { - const comp = this.getComponentByWorkflowTaskOption(this.type); - if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; - viewContainerRef.clear(); - viewContainerRef.createComponent(componentFactory); + if (hasValue(this.getComponent())) { + super.ngOnInit(); } else { void this.router.navigate([PAGE_NOT_FOUND_PATH]); } } - getComponentByWorkflowTaskOption(type: string): any { - return getAdvancedComponentByWorkflowTaskOption(type); + public getComponent(): GenericConstructor { + return getAdvancedComponentByWorkflowTaskOption(this.type); } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts deleted file mode 100644 index e569f6cc6f8..00000000000 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsAdvancedWorkflowActions]', -}) -/** - * Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component - */ -export class AdvancedWorkflowActionsDirective { - - constructor( - public viewContainerRef: ViewContainerRef, - ) { - } - -} diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts index cf998c52743..c2bd3d5ad76 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -23,9 +23,6 @@ import { import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; -import { - AdvancedWorkflowActionsDirective -} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive'; import { AccessControlModule } from '../access-control/access-control.module'; import { ReviewersListComponent @@ -54,7 +51,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; AdvancedWorkflowActionRatingComponent, AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionPageComponent, - AdvancedWorkflowActionsDirective, ReviewersListComponent, ] }) From 2bae174350689887de4b856ab3ebae20b2389586 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 16:14:54 +0200 Subject: [PATCH 12/66] Make MetadataRepresentationLoaderComponent extend AbstractComponentLoaderComponent --- ...adata-representation-loader.component.html | 1 - ...ta-representation-loader.component.spec.ts | 18 ++-- ...etadata-representation-loader.component.ts | 99 ++----------------- .../metadata-representation.directive.ts | 11 --- src/app/shared/shared.module.ts | 2 - 5 files changed, 20 insertions(+), 111 deletions(-) delete mode 100644 src/app/shared/metadata-representation/metadata-representation-loader.component.html delete mode 100644 src/app/shared/metadata-representation/metadata-representation.directive.ts diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.html b/src/app/shared/metadata-representation/metadata-representation-loader.component.html deleted file mode 100644 index 3979c238adf..00000000000 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts index 7edf1a700e5..c9bec402d16 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts @@ -6,10 +6,11 @@ import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model'; import { MetadataRepresentationLoaderComponent } from './metadata-representation-loader.component'; -import { MetadataRepresentationDirective } from './metadata-representation.directive'; +import { DynamicComponentLoaderDirective } from '../abstract-component-loader/dynamic-component-loader.directive'; import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator'; import { ThemeService } from '../theme-support/theme.service'; import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component'; +import { getMockThemeService } from '../mocks/theme-service.mock'; const testType = 'TestType'; const testContext = Context.Search; @@ -36,12 +37,14 @@ describe('MetadataRepresentationLoaderComponent', () => { const themeName = 'test-theme'; beforeEach(waitForAsync(() => { - themeService = jasmine.createSpyObj('themeService', { - getThemeName: themeName, - }); + themeService = getMockThemeService(themeName); TestBed.configureTestingModule({ imports: [], - declarations: [MetadataRepresentationLoaderComponent, PlainTextMetadataListElementComponent, MetadataRepresentationDirective], + declarations: [ + MetadataRepresentationLoaderComponent, + PlainTextMetadataListElementComponent, + DynamicComponentLoaderDirective, + ], schemas: [NO_ERRORS_SCHEMA], providers: [ { @@ -64,6 +67,7 @@ describe('MetadataRepresentationLoaderComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(MetadataRepresentationLoaderComponent); comp = fixture.componentInstance; + spyOn(comp, 'getComponent').and.callThrough(); comp.mdRepresentation = new TestType(); comp.context = testContext; @@ -71,8 +75,8 @@ describe('MetadataRepresentationLoaderComponent', () => { })); describe('When the component is rendered', () => { - it('should call the getMetadataRepresentationComponent function with the right entity type, representation type and context', () => { - expect((comp as any).getMetadataRepresentationComponent).toHaveBeenCalledWith(testType, testRepresentationType, testContext, themeName); + it('should call the getComponent function', () => { + expect(comp.getComponent).toHaveBeenCalled(); }); }); }); diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 42ee093278a..83542512ed7 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild, OnChanges, SimpleChanges, ComponentRef, ViewContainerRef, ComponentFactory } from '@angular/core'; +import { Component, Inject, Input } from '@angular/core'; import { MetadataRepresentation, MetadataRepresentationType @@ -7,118 +7,37 @@ import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representa import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component'; -import { MetadataRepresentationDirective } from './metadata-representation.directive'; -import { hasValue, isNotEmpty, hasNoValue } from '../empty.util'; import { ThemeService } from '../theme-support/theme.service'; +import { AbstractComponentLoaderComponent } from '../abstract-component-loader/abstract-component-loader.component'; @Component({ selector: 'ds-metadata-representation-loader', - templateUrl: './metadata-representation-loader.component.html' + templateUrl: '../abstract-component-loader/abstract-component-loader.component.html', }) /** * Component for determining what component to use depending on the item's entity type (dspace.entity.type), its metadata representation and, optionally, its context */ -export class MetadataRepresentationLoaderComponent implements OnInit, OnChanges { +export class MetadataRepresentationLoaderComponent extends AbstractComponentLoaderComponent { /** * The item or metadata to determine the component for */ - private _mdRepresentation: MetadataRepresentation; - get mdRepresentation(): MetadataRepresentation { - return this._mdRepresentation; - } - @Input() set mdRepresentation(nextValue: MetadataRepresentation) { - this._mdRepresentation = nextValue; - if (hasValue(this.compRef?.instance)) { - this.compRef.instance.mdRepresentation = nextValue; - } - } - - /** - * The optional context - */ - @Input() context: Context; - - /** - * Directive to determine where the dynamic child component is located - */ - @ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective; - - /** - * The reference to the dynamic component - */ - protected compRef: ComponentRef; + @Input() mdRepresentation: MetadataRepresentation; protected inAndOutputNames: (keyof this)[] = [ - 'context', + ...this.inAndOutputNames, 'mdRepresentation', ]; constructor( - private componentFactoryResolver: ComponentFactoryResolver, - private themeService: ThemeService, + protected themeService: ThemeService, @Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor, ) { + super(themeService); } - /** - * Set up the dynamic child component - */ - ngOnInit(): void { - this.instantiateComponent(); - } - - /** - * Whenever the inputs change, update the inputs of the dynamic component - */ - ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(changes); - } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } - } - } - } - - private instantiateComponent(changes?: SimpleChanges): void { - const componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); - - const viewContainerRef: ViewContainerRef = this.mdRepDirective.viewContainerRef; - viewContainerRef.clear(); - - this.compRef = viewContainerRef.createComponent(componentFactory); - - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); - } - } - - /** - * Fetch the component depending on the item's entity type, metadata representation type and context - * @returns {string} - */ - private getComponent(): GenericConstructor { + public getComponent(): GenericConstructor { return this.getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName()); } - /** - * Connect the in and outputs of this component to the dynamic component, - * to ensure they're in sync - */ - protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { - this.compRef.instance[name] = this[name]; - }); - } - } } diff --git a/src/app/shared/metadata-representation/metadata-representation.directive.ts b/src/app/shared/metadata-representation/metadata-representation.directive.ts deleted file mode 100644 index 9ff0573baff..00000000000 --- a/src/app/shared/metadata-representation/metadata-representation.directive.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsMetadataRepresentation]', -}) -/** - * Directive used as a hook to know where to inject the dynamic metadata representation component - */ -export class MetadataRepresentationDirective { - constructor(public viewContainerRef: ViewContainerRef) { } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c00b09e20fc..bce7282e8b5 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -170,7 +170,6 @@ import { AccessStatusBadgeComponent } from './object-collection/shared/badges/ac import { MetadataRepresentationLoaderComponent } from './metadata-representation/metadata-representation-loader.component'; -import { MetadataRepresentationDirective } from './metadata-representation/metadata-representation.directive'; import { ListableObjectComponentLoaderComponent } from './object-collection/shared/listable-object/listable-object-component-loader.component'; @@ -481,7 +480,6 @@ const DIRECTIVES = [ InListValidator, AutoFocusDirective, RoleDirective, - MetadataRepresentationDirective, FileValueAccessorDirective, FileValidator, NgForTrackByIdDirective, From 26e0bc81c7a58548d8557a3a672891b107f250d5 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 9 Dec 2023 22:11:50 +0100 Subject: [PATCH 13/66] Removed Themed components from PR 1842 because those should be themed using the decorator rendersBrowseBy(browseType, themeName) and fixed the components in the custom theme folder - Removed ngOnDestroy from BrowseByTitlePageComponent because super.ngOnDestroy already included the unsubscribe functionality - Removed BrowseBySwitcherComponent since a themed version of that isn't really useful --- .../browse-by-date-page.component.ts | 6 ++-- .../themed-browse-by-date-page.component.ts | 29 ------------------- .../browse-by-metadata-page.component.ts | 2 ++ ...hemed-browse-by-metadata-page.component.ts | 29 ------------------- .../browse-by-switcher/browse-by-decorator.ts | 3 +- .../themed-browse-by-switcher.component.ts | 28 ------------------ .../browse-by-taxonomy-page.component.ts | 3 +- ...hemed-browse-by-taxonomy-page.component.ts | 28 ------------------ .../browse-by-title-page.component.ts | 11 +++---- .../themed-browse-by-title-page.component.ts | 29 ------------------- src/app/browse-by/browse-by.module.ts | 9 ------ .../browse-by-date-page.component.ts | 7 ++--- .../browse-by-metadata-page.component.ts | 7 ++--- .../browse-by-switcher.component.ts | 15 ---------- .../browse-by-taxonomy-page.component.ts | 5 ++-- .../browse-by-title-page.component.ts | 7 ++--- src/themes/custom/lazy-theme.module.ts | 2 -- 17 files changed, 22 insertions(+), 198 deletions(-) delete mode 100644 src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts delete mode 100644 src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts delete mode 100644 src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts delete mode 100644 src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts delete mode 100644 src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts delete mode 100644 src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 7074190e1eb..b1068033518 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; import { BrowseByMetadataPageComponent, browseParamsToOptions, @@ -19,6 +19,7 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-date-page', @@ -30,7 +31,8 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; * A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields. * An example would be 'dateissued' for 'dc.date.issued' */ -export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { +@rendersBrowseBy(BrowseByDataType.Date) +export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent implements OnInit { /** * The default metadata keys to use for determining the lower limit of the StartsWith dropdown options diff --git a/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts deleted file mode 100644 index 8eeae0c5de5..00000000000 --- a/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Component} from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseByDatePageComponent } from './browse-by-date-page.component'; -import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; - -/** - * Themed wrapper for BrowseByDatePageComponent - * */ -@Component({ - selector: 'ds-themed-browse-by-metadata-page', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', -}) - -@rendersBrowseBy(BrowseByDataType.Date) -export class ThemedBrowseByDatePageComponent - extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseByDatePageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-date-page/browse-by-date-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-date-page.component`); - } -} diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 113bc67c924..28d57ce3a4a 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -22,6 +22,7 @@ import { Collection } from '../../core/shared/collection.model'; import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; export const BBM_PAGINATION_ID = 'bbm'; @@ -36,6 +37,7 @@ export const BBM_PAGINATION_ID = 'bbm'; * or multiple metadata fields. An example would be 'author' for * 'dc.contributor.*' */ +@rendersBrowseBy(BrowseByDataType.Metadata) export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { /** diff --git a/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts deleted file mode 100644 index b0679258e91..00000000000 --- a/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Component} from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseByMetadataPageComponent } from './browse-by-metadata-page.component'; -import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; - -/** - * Themed wrapper for BrowseByMetadataPageComponent - **/ -@Component({ - selector: 'ds-themed-browse-by-metadata-page', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', -}) - -@rendersBrowseBy(BrowseByDataType.Metadata) -export class ThemedBrowseByMetadataPageComponent - extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseByMetadataPageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-metadata-page.component`); - } -} diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index b59a46cae11..11ff1cec288 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -9,7 +9,8 @@ import { export enum BrowseByDataType { Title = 'title', Metadata = 'text', - Date = 'date' + Date = 'date', + Hierarchy = 'hierarchy', } export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; diff --git a/src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts deleted file mode 100644 index 0187d4e3c5e..00000000000 --- a/src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component } from '@angular/core'; - -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; - -/** - * Themed wrapper for BrowseBySwitcherComponent - */ -@Component({ - selector: 'ds-themed-browse-by-switcher', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html' -}) -export class ThemedBrowseBySwitcherComponent extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseBySwitcherComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-switcher/browse-by-switcher.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-switcher.component`); - } - - -} diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index cf6345bf394..3536d92e47b 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -5,7 +5,7 @@ import { ActivatedRoute } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { BROWSE_BY_COMPONENT_FACTORY } from '../browse-by-switcher/browse-by-decorator'; +import { BROWSE_BY_COMPONENT_FACTORY, rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; import { ThemeService } from 'src/app/shared/theme-support/theme.service'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; @@ -18,6 +18,7 @@ import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-bro /** * Component for browsing items by metadata in a hierarchical controlled vocabulary */ +@rendersBrowseBy(BrowseByDataType.Hierarchy) export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { /** diff --git a/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts deleted file mode 100644 index 212044b8539..00000000000 --- a/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component } from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; -import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component'; - -@Component({ - selector: 'ds-themed-browse-by-taxonomy-page', - templateUrl: '../../shared/theme-support/themed.component.html', - styleUrls: [] -}) -/** - * Themed wrapper for BrowseByTaxonomyPageComponent - */ -@rendersBrowseBy('hierarchy') -export class ThemedBrowseByTaxonomyPageComponent extends ThemedComponent{ - - protected getComponentName(): string { - return 'BrowseByTaxonomyPageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-taxonomy-page.component`); - } -} diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 58df79ebe85..cec30712da1 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,7 +1,6 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; -import { Component, Inject } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { hasValue } from '../../shared/empty.util'; import { BrowseByMetadataPageComponent, browseParamsToOptions, getBrowseSearchOptions @@ -14,6 +13,7 @@ import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-title-page', @@ -23,7 +23,8 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; /** * Component for browsing items by title (dc.title) */ -export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { +@rendersBrowseBy(BrowseByDataType.Title) +export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent implements OnInit { public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, @@ -57,8 +58,4 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { this.updateStartsWithTextOptions(); } - ngOnDestroy(): void { - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); - } - } diff --git a/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts deleted file mode 100644 index 4a1bcc0bc11..00000000000 --- a/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Component} from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseByTitlePageComponent } from './browse-by-title-page.component'; -import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; - -/** - * Themed wrapper for BrowseByTitlePageComponent - */ -@Component({ - selector: 'ds-themed-browse-by-title-page', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', -}) - -@rendersBrowseBy(BrowseByDataType.Title) -export class ThemedBrowseByTitlePageComponent - extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseByTitlePageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-title-page/browse-by-title-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-title-page.component`); - } -} diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index c0e2d3f9ff8..bc7f6a159af 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -7,10 +7,6 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switch import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; -import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component'; -import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component'; -import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component'; -import { ThemedBrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; import { FormModule } from '../shared/form/form.module'; @@ -21,11 +17,6 @@ const ENTRY_COMPONENTS = [ BrowseByMetadataPageComponent, BrowseByDatePageComponent, BrowseByTaxonomyPageComponent, - - ThemedBrowseByMetadataPageComponent, - ThemedBrowseByDatePageComponent, - ThemedBrowseByTitlePageComponent, - ThemedBrowseByTaxonomyPageComponent, ]; @NgModule({ diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 9fcf7733504..589effde609 100644 --- a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-date-page', @@ -8,10 +9,6 @@ import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/b // templateUrl: './browse-by-date-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ - +@rendersBrowseBy(BrowseByDataType.Date, 'custom') export class BrowseByDatePageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 9434ca936dc..d2ee7ad6940 100644 --- a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -8,10 +9,6 @@ import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../a // templateUrl: './browse-by-metadata-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ - +@rendersBrowseBy(BrowseByDataType.Metadata, 'custom') export class BrowseByMetadataPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts deleted file mode 100644 index e65997eaf4c..00000000000 --- a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from '@angular/core'; -import { BrowseBySwitcherComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-switcher/browse-by-switcher.component'; - -@Component({ - selector: 'ds-browse-by-switcher', - // styleUrls: ['./browse-by-switcher.component.scss'], - // templateUrl: './browse-by-switcher.component.html' - templateUrl: '../../../../../app/browse-by/browse-by-switcher/browse-by-switcher.component.html' -}) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ -export class BrowseBySwitcherComponent extends BaseComponent {} - diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 34d80a0cb8a..459dc54d7e0 100644 --- a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -8,8 +9,6 @@ import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../a // styleUrls: ['./browse-by-taxonomy-page.component.scss'], styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss'], }) -/** - * Component for browsing items by metadata in a hierarchical controlled vocabulary - */ +@rendersBrowseBy(BrowseByDataType.Hierarchy, 'custom') export class BrowseByTaxonomyPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index ed96a81110b..136970b38a0 100644 --- a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-title-page', @@ -8,10 +9,6 @@ import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/ // templateUrl: './browse-by-title-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ - +@rendersBrowseBy(BrowseByDataType.Title, 'custom') export class BrowseByTitlePageComponent extends BaseComponent { } diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index edb3f5478c9..e1020893612 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -46,7 +46,6 @@ import { RootModule } from '../../app/root.module'; import { FileSectionComponent } from './app/item-page/simple/field-components/file-section/file-section.component'; import { HomePageComponent } from './app/home-page/home-page.component'; import { RootComponent } from './app/root/root.component'; -import { BrowseBySwitcherComponent } from './app/browse-by/browse-by-switcher/browse-by-switcher.component'; import { CommunityListPageComponent } from './app/community-list-page/community-list-page.component'; import { SearchPageComponent } from './app/search-page/search-page.component'; import { ConfigurationSearchPageComponent } from './app/search-page/configuration-search-page.component'; @@ -161,7 +160,6 @@ const DECLARATIONS = [ FileSectionComponent, HomePageComponent, RootComponent, - BrowseBySwitcherComponent, CommunityListPageComponent, SearchPageComponent, ConfigurationSearchPageComponent, From 14d42b0d93a18e129c0cb63af695592e825e7b74 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 10 Dec 2023 22:19:57 +0100 Subject: [PATCH 14/66] Fixed getComponent not triggering again when it's input values change by creating inputNamesDependentForComponent Also simplified the way @Input() values are passed to their child component and how ngOnChanges is called by using setInput instead --- .../abstract-component-loader.component.ts | 61 ++++++++++++++----- ...etadata-representation-loader.component.ts | 4 +- .../claimed-task-actions-loader.component.ts | 12 ++-- ...table-object-component-loader.component.ts | 26 ++++---- ...anced-workflow-actions-loader.component.ts | 4 +- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 6f934f5b4bf..46074f7a6b1 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -32,10 +32,22 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC */ protected subs: Subscription[] = []; - protected inAndOutputNames: (keyof this)[] = [ + /** + * The @{@link Input}() that are used to find the matching component using {@link getComponent}. When the value of + * one of these @{@link Input}() change this loader needs to retrieve the best matching component again using the + * {@link getComponent} method. + */ + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + 'context', + ]; + + protected inputNames: (keyof this & string)[] = [ 'context', ]; + protected outputNames: (keyof this & string)[] = [ + ]; + constructor( protected themeService: ThemeService, ) { @@ -45,7 +57,9 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Set up the dynamic child component */ ngOnInit(): void { - this.instantiateComponent(); + if (hasNoValue(this.compRef)) { + this.instantiateComponent(); + } } /** @@ -55,14 +69,14 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC if (hasNoValue(this.compRef)) { // sometimes the component has not been initialized yet, so it first needs to be initialized // before being called again - this.instantiateComponent(changes); + this.instantiateComponent(); } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + if (this.inputNamesDependentForComponent.some((name: any) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { + // Recreate the component when the @Input()s used by getComponent() aren't up-to-date anymore + this.destroyComponentInstance(); + this.instantiateComponent(); + } else { this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } } } } @@ -73,7 +87,10 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC .forEach((subscription: Subscription) => subscription.unsubscribe()); } - public instantiateComponent(changes?: SimpleChanges): void { + /** + * Creates the component and connects the @Input() & @Output() from the ThemedComponent to its child Component. + */ + public instantiateComponent(): void { const component: GenericConstructor = this.getComponent(); const viewContainerRef: ViewContainerRef = this.componentDirective.viewContainerRef; @@ -86,10 +103,16 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC }, ); - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); + this.connectInputsAndOutputs(); + } + + /** + * Destroys the themed component and calls it's `ngOnDestroy` + */ + public destroyComponentInstance(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = null; } } @@ -99,12 +122,18 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC public abstract getComponent(): GenericConstructor; /** - * Connect the in and outputs of this component to the dynamic component, + * Connect the inputs and outputs of this component to the dynamic component, * to ensure they're in sync */ protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { + if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { + // Using setInput will automatically trigger the ngOnChanges + this.compRef.setInput(name, this[name]); + }); + } + if (isNotEmpty(this.outputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.outputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { this.compRef.instance[name] = this[name]; }); } diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 83542512ed7..4b63a31a2b4 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -24,8 +24,8 @@ export class MetadataRepresentationLoaderComponent extends AbstractComponentLoad */ @Input() mdRepresentation: MetadataRepresentation; - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'mdRepresentation', ]; diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index 75b392fe516..fb39347d63c 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, } from '@angular/core'; +import { Component, EventEmitter, Input, Output, } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; @@ -16,7 +16,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor' * Component for loading a ClaimedTaskAction component depending on the "option" input * Passes on the ClaimedTask to the component and subscribes to the processCompleted output */ -export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit, OnChanges { +export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent { /** * The item object that belonging to the ClaimedTask object */ @@ -46,12 +46,16 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'item', 'object', 'option', 'workflowitem', + ]; + + protected outputNames: (keyof this & string)[] = [ + ...this.outputNames, 'processCompleted', ]; diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index f62ee44a1c2..a83cdfb6b2a 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,5 +1,4 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { take } from 'rxjs/operators'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; @@ -74,8 +73,8 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'object', 'index', 'linkType', @@ -84,6 +83,10 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa 'showThumbnails', 'viewMode', 'value', + ]; + + protected outputNames: (keyof this & string)[] = [ + ...this.outputNames, 'contentChange', ]; @@ -94,17 +97,16 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa super(themeService); } - public instantiateComponent(changes?: SimpleChanges): void { - super.instantiateComponent(changes); + public instantiateComponent(): void { + super.instantiateComponent(); if ((this.compRef.instance as any).reloadedObject) { - combineLatest([ - observableOf(changes), - (this.compRef.instance as any).reloadedObject.pipe(take(1)) as Observable, - ]).subscribe(([simpleChanges, reloadedObject]: [SimpleChanges, DSpaceObject]) => { + (this.compRef.instance as any).reloadedObject.pipe( + take(1), + ).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { - this.compRef.destroy(); + this.destroyComponentInstance(); this.object = reloadedObject; - this.instantiateComponent(simpleChanges); + this.instantiateComponent(); this.cdr.detectChanges(); this.contentChange.emit(reloadedObject); } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index 10cae7ec7df..1db49b97e8d 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -24,8 +24,8 @@ export class AdvancedWorkflowActionsLoaderComponent extends AbstractComponentLoa */ @Input() type: string; - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'type', ]; From 7b8962a7cde749074df3c47e93e780fb21c895ca Mon Sep 17 00:00:00 2001 From: Thomas Misilo Date: Mon, 11 Dec 2023 13:16:46 -0600 Subject: [PATCH 15/66] Update "E-mail" to be "Email" for consistency --- .../privacy-content/privacy-content.component.html | 2 +- src/assets/i18n/en.json5 | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.html b/src/app/info/privacy/privacy-content/privacy-content.component.html index f29a786e8b3..003e6b0d51a 100644 --- a/src/app/info/privacy/privacy-content/privacy-content.component.html +++ b/src/app/info/privacy/privacy-content/privacy-content.component.html @@ -22,7 +22,7 @@

Children under the age of 13

Information we collect about you and how we collect it

We collect several types of information from and about users of our Website, including information:

    -
  • by which you may be personally identified, such as name, e-mail address, telephone number, or any other identifier by which you may be contacted online or offline ("personal information"); and/or
  • +
  • by which you may be personally identified, such as name, email address, telephone number, or any other identifier by which you may be contacted online or offline ("personal information"); and/or
  • about your internet connection, the equipment you use to access our Website and usage details.

We collect this information:

diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 7bf16c4ee8b..515e0338f1f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -266,7 +266,7 @@ "admin.access-control.epeople.search.scope.metadata": "Metadata", - "admin.access-control.epeople.search.scope.email": "E-mail (exact)", + "admin.access-control.epeople.search.scope.email": "Email (exact)", "admin.access-control.epeople.search.button": "Search", @@ -278,7 +278,7 @@ "admin.access-control.epeople.table.name": "Name", - "admin.access-control.epeople.table.email": "E-mail (exact)", + "admin.access-control.epeople.table.email": "Email (exact)", "admin.access-control.epeople.table.edit": "Edit", @@ -298,9 +298,9 @@ "admin.access-control.epeople.form.lastName": "Last name", - "admin.access-control.epeople.form.email": "E-mail", + "admin.access-control.epeople.form.email": "Email", - "admin.access-control.epeople.form.emailHint": "Must be a valid e-mail address", + "admin.access-control.epeople.form.emailHint": "Must be a valid email address", "admin.access-control.epeople.form.canLogIn": "Can log in", @@ -750,7 +750,7 @@ "bitstream-request-a-copy.name.error": "The name is required", - "bitstream-request-a-copy.email.label": "Your e-mail address *", + "bitstream-request-a-copy.email.label": "Your email address *", "bitstream-request-a-copy.email.hint": "This email address is used for sending the file.", @@ -1602,9 +1602,9 @@ "error.validation.required": "This field is required", - "error.validation.NotValidEmail": "This is not a valid e-mail", + "error.validation.NotValidEmail": "This is not a valid email", - "error.validation.emailTaken": "This e-mail is already taken", + "error.validation.emailTaken": "This email is already taken", "error.validation.groupExists": "This group already exists", From a83d69ee0eaf9233398d64344367ba5cc41a289b Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 11 Dec 2023 01:56:25 +0100 Subject: [PATCH 16/66] Created BrowseByPageComponent that uses the new refactored BrowseBySwitcherComponent extending AbstractComponentLoaderComponent - Added the context to the rendersBrowseBy decorator - Created AbstractBrowseByTypeComponent that is extended by all the browse type sections --- .../abstract-browse-by-type.component.ts | 32 +++++++++ .../browse-by-metadata-page.component.ts | 14 ++-- src/app/browse-by/browse-by-page.module.ts | 16 ++++- .../browse-by-page.component.html | 2 + .../browse-by-page.component.scss} | 0 .../browse-by-page.component.spec.ts | 69 +++++++++++++++++++ .../browse-by-page.component.ts | 31 +++++++++ src/app/browse-by/browse-by-routing.module.ts | 4 +- .../browse-by-switcher/browse-by-decorator.ts | 38 ++++++---- .../browse-by-switcher.component.html | 1 - .../browse-by-switcher.component.spec.ts | 57 ++++++++------- .../browse-by-switcher.component.ts | 45 +++++------- .../browse-by-taxonomy-page.component.ts | 35 ++++------ src/app/browse-by/browse-by.module.ts | 11 +-- .../core/shared/browse-definition.model.ts | 3 +- .../shared/flat-browse-definition.model.ts | 3 +- .../hierarchical-browse-definition.model.ts | 5 +- .../value-list-browse-definition.model.ts | 3 +- .../abstract-component-loader.component.ts | 2 +- ...-object-component-loader.component.spec.ts | 2 +- .../browse-by-date-page.component.ts | 3 +- .../browse-by-metadata-page.component.ts | 3 +- .../browse-by-switcher.component.scss | 0 .../browse-by-taxonomy-page.component.ts | 3 +- .../browse-by-title-page.component.ts | 3 +- 25 files changed, 269 insertions(+), 116 deletions(-) create mode 100644 src/app/browse-by/abstract-browse-by-type.component.ts create mode 100644 src/app/browse-by/browse-by-page/browse-by-page.component.html rename src/{themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.html => app/browse-by/browse-by-page/browse-by-page.component.scss} (100%) create mode 100644 src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts create mode 100644 src/app/browse-by/browse-by-page/browse-by-page.component.ts delete mode 100644 src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html delete mode 100644 src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.scss diff --git a/src/app/browse-by/abstract-browse-by-type.component.ts b/src/app/browse-by/abstract-browse-by-type.component.ts new file mode 100644 index 00000000000..246e59a010a --- /dev/null +++ b/src/app/browse-by/abstract-browse-by-type.component.ts @@ -0,0 +1,32 @@ +import { Component, Input, OnDestroy } from '@angular/core'; +import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator'; +import { Context } from '../core/shared/context.model'; +import { Subscription } from 'rxjs'; +import { hasValue } from '../shared/empty.util'; + +@Component({ + selector: 'ds-abstract-browse-by-type', + template: '', +}) +export abstract class AbstractBrowseByTypeComponent implements OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link BrowseByDataType} of this Component + */ + @Input() browseByType: BrowseByDataType; + + /** + * List of subscriptions + */ + subs: Subscription[] = []; + + ngOnDestroy(): void { + this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe()); + } + +} diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 28d57ce3a4a..225b9b503c5 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; @@ -23,6 +23,7 @@ import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; export const BBM_PAGINATION_ID = 'bbm'; @@ -38,7 +39,7 @@ export const BBM_PAGINATION_ID = 'bbm'; * 'dc.contributor.*' */ @rendersBrowseBy(BrowseByDataType.Metadata) -export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { +export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { /** * The list of browse-entries to display @@ -75,11 +76,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ currentSort$: Observable; - /** - * List of subscriptions - */ - subs: Subscription[] = []; - /** * The default browse id to resort to when none is provided */ @@ -132,7 +128,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { @Inject(APP_CONFIG) public appConfig: AppConfig, public dsoNameService: DSONameService, ) { - + super(); this.fetchThumbnails = this.appConfig.browseBy.showThumbnails; this.paginationConfig = Object.assign(new PaginationComponentOptions(), { id: BBM_PAGINATION_ID, @@ -276,7 +272,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + super.ngOnDestroy(); this.paginationService.clearPagination(this.paginationConfig.id); } diff --git a/src/app/browse-by/browse-by-page.module.ts b/src/app/browse-by/browse-by-page.module.ts index 554a6c4f466..8a010f71056 100644 --- a/src/app/browse-by/browse-by-page.module.ts +++ b/src/app/browse-by/browse-by-page.module.ts @@ -5,12 +5,19 @@ import { ItemDataService } from '../core/data/item-data.service'; import { BrowseService } from '../core/browse/browse.service'; import { BrowseByGuard } from './browse-by-guard'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; +import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component'; +import { SharedModule } from '../shared/shared.module'; + +const DECLARATIONS = [ + BrowseByPageComponent, +]; @NgModule({ imports: [ SharedBrowseByModule, BrowseByRoutingModule, - BrowseByModule.withEntryComponents(), + BrowseByModule, + SharedModule, ], providers: [ ItemDataService, @@ -18,8 +25,11 @@ import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.modul BrowseByGuard, ], declarations: [ - - ] + ...DECLARATIONS, + ], + exports: [ + ...DECLARATIONS, + ], }) export class BrowseByPageModule { diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.html b/src/app/browse-by/browse-by-page/browse-by-page.component.html new file mode 100644 index 00000000000..b7b109643b0 --- /dev/null +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.html b/src/app/browse-by/browse-by-page/browse-by-page.component.scss similarity index 100% rename from src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.html rename to src/app/browse-by/browse-by-page/browse-by-page.component.scss diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts new file mode 100644 index 00000000000..d60ca02afae --- /dev/null +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts @@ -0,0 +1,69 @@ +// eslint-disable-next-line max-classes-per-file +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowseByPageComponent } from './browse-by-page.component'; +import { BrowseBySwitcherComponent } from '../browse-by-switcher/browse-by-switcher.component'; +import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; +import { ActivatedRoute } from '@angular/router'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; +import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; +import { ThemeService } from '../../shared/theme-support/theme.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { Component } from '@angular/core'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { By } from '@angular/platform-browser'; + +@rendersBrowseBy('BrowseByPageComponent' as BrowseByDataType) +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', + template: '', +}) +class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +} + +class TestBrowseByPageBrowseDefinition extends BrowseDefinition { + getRenderType(): BrowseByDataType { + return 'BrowseByPageComponent' as BrowseByDataType; + } +} + +describe('BrowseByPageComponent', () => { + let component: BrowseByPageComponent; + let fixture: ComponentFixture; + + let activatedRoute: ActivatedRouteStub; + let themeService: ThemeService; + + beforeEach(async () => { + activatedRoute = new ActivatedRouteStub(); + themeService = getMockThemeService(); + + await TestBed.configureTestingModule({ + declarations: [ + BrowseByPageComponent, + BrowseBySwitcherComponent, + DynamicComponentLoaderDirective, + ], + providers: [ + BrowseByTestComponent, + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: ThemeService, useValue: themeService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BrowseByPageComponent); + component = fixture.componentInstance; + }); + + it('should create the correct browse section based on the route browseDefinition', () => { + activatedRoute.testData = { + browseDefinition: new TestBrowseByPageBrowseDefinition(), + }; + + fixture.detectChanges(); + + expect(component).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#BrowseByTestComponent'))).not.toBeNull(); + }); +}); diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.ts new file mode 100644 index 00000000000..ad261334856 --- /dev/null +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; + +@Component({ + selector: 'ds-browse-by-page', + templateUrl: './browse-by-page.component.html', + styleUrls: ['./browse-by-page.component.scss'], +}) +export class BrowseByPageComponent implements OnInit { + + browseByType$: Observable; + + constructor( + protected route: ActivatedRoute, + ) { + } + + /** + * Fetch the correct browse-by component by using the relevant config from the route data + */ + ngOnInit(): void { + this.browseByType$ = this.route.data.pipe( + map((data: { browseDefinition: BrowseDefinition }) => data.browseDefinition.getRenderType()), + ); + } + +} diff --git a/src/app/browse-by/browse-by-routing.module.ts b/src/app/browse-by/browse-by-routing.module.ts index bb67dc65aed..f07df26b32b 100644 --- a/src/app/browse-by/browse-by-routing.module.ts +++ b/src/app/browse-by/browse-by-routing.module.ts @@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'; import { BrowseByGuard } from './browse-by-guard'; import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver'; import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver'; -import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; +import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component'; import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; @NgModule({ @@ -18,7 +18,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; children: [ { path: ':id', - component: ThemedBrowseBySwitcherComponent, + component: BrowseByPageComponent, canActivate: [BrowseByGuard], resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver }, data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' } diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index 11ff1cec288..6891fba0036 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -1,10 +1,9 @@ import { hasNoValue } from '../../shared/empty.util'; import { InjectionToken } from '@angular/core'; +import { DEFAULT_THEME, resolveTheme } from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { - DEFAULT_THEME, - resolveTheme -} from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; export enum BrowseByDataType { Title = 'title', @@ -14,28 +13,36 @@ export enum BrowseByDataType { } export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; +export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any; -export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType, theme) => GenericConstructor>('getComponentByBrowseByType', { +export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType: BrowseByDataType, context: Context, theme: string) => GenericConstructor>('getComponentByBrowseByType', { providedIn: 'root', factory: () => getComponentByBrowseByType }); -const map = new Map(); +const map: Map>>> = new Map(); /** * Decorator used for rendering Browse-By pages by type * @param browseByType The type of page + * @param context The optional context for the component * @param theme The optional theme for the component */ -export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) { +export function rendersBrowseBy(browseByType: BrowseByDataType, context = DEFAULT_BROWSE_BY_CONTEXT, theme = DEFAULT_THEME) { return function decorator(component: any) { + if (hasNoValue(browseByType)) { + return; + } if (hasNoValue(map.get(browseByType))) { map.set(browseByType, new Map()); } - if (hasNoValue(map.get(browseByType).get(theme))) { - map.get(browseByType).set(theme, component); + if (hasNoValue(map.get(browseByType).get(context))) { + map.get(browseByType).set(context, new Map()); + } + if (hasNoValue(map.get(browseByType).get(context).get(theme))) { + map.get(browseByType).get(context).set(theme, component); } else { - throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}" and theme "${theme}"`); + throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}", context "${context}" and theme "${theme}"`); } }; } @@ -43,12 +50,17 @@ export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) { /** * Get the component used for rendering a Browse-By page by type * @param browseByType The type of page + * @param context The context to match * @param theme the theme to match */ -export function getComponentByBrowseByType(browseByType, theme) { - let themeMap = map.get(browseByType); +export function getComponentByBrowseByType(browseByType: BrowseByDataType, context: Context, theme: string): GenericConstructor { + let contextMap: Map>> = map.get(browseByType); + if (hasNoValue(contextMap)) { + contextMap = map.get(DEFAULT_BROWSE_BY_TYPE); + } + let themeMap: Map> = contextMap.get(context); if (hasNoValue(themeMap)) { - themeMap = map.get(DEFAULT_BROWSE_BY_TYPE); + themeMap = contextMap.get(DEFAULT_BROWSE_BY_CONTEXT); } const comp = resolveTheme(themeMap, theme); if (hasNoValue(comp)) { diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html deleted file mode 100644 index afe79cf2b10..00000000000 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts index c13405dd4d2..5812a54269b 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts @@ -1,13 +1,23 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator'; -import { BehaviorSubject } from 'rxjs'; +import { SimpleChange, Component } from '@angular/core'; +import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator'; import { ThemeService } from '../../shared/theme-support/theme.service'; import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model'; import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model'; import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchical-browse-definition'; +import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; +import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; + +@rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType) +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', + template: '', +}) +class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +} describe('BrowseBySwitcherComponent', () => { let comp: BrowseBySwitcherComponent; @@ -41,46 +51,45 @@ describe('BrowseBySwitcherComponent', () => { ), ]; - const data = new BehaviorSubject(createDataWithBrowseDefinition(new FlatBrowseDefinition())); - - const activatedRouteStub = { - data - }; - let themeService: ThemeService; - let themeName: string; + const themeName = 'dspace'; beforeEach(waitForAsync(() => { - themeName = 'dspace'; - themeService = jasmine.createSpyObj('themeService', { - getThemeName: themeName, - }); + themeService = getMockThemeService(themeName); - TestBed.configureTestingModule({ - declarations: [BrowseBySwitcherComponent], + void TestBed.configureTestingModule({ + declarations: [ + BrowseBySwitcherComponent, + DynamicComponentLoaderDirective, + ], providers: [ - { provide: ActivatedRoute, useValue: activatedRouteStub }, + BrowseByTestComponent, { provide: ThemeService, useValue: themeService }, - { provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) } ], - schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(BrowseBySwitcherComponent); comp = fixture.componentInstance; + spyOn(comp, 'getComponent').and.returnValue(BrowseByTestComponent); + spyOn(comp, 'connectInputsAndOutputs').and.callThrough(); })); types.forEach((type: NonHierarchicalBrowseDefinition) => { describe(`when switching to a browse-by page for "${type.id}"`, () => { - beforeEach(() => { - data.next(createDataWithBrowseDefinition(type)); + beforeEach(async () => { + comp.browseByType = type.dataType; + comp.ngOnChanges({ + browseByType: new SimpleChange(undefined, type.dataType, true), + }); fixture.detectChanges(); + await fixture.whenStable(); }); - it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => { - expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType, themeName); + it(`should call getComponent with type "${type.dataType}"`, () => { + expect(comp.getComponent).toHaveBeenCalled(); + expect(comp.connectInputsAndOutputs).toHaveBeenCalled(); }); }); }); diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 35e4edf9005..881df713112 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -1,38 +1,29 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator'; +import { Component, Input } from '@angular/core'; +import { getComponentByBrowseByType, BrowseByDataType } from './browse-by-decorator'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { ThemeService } from '../../shared/theme-support/theme.service'; +import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; @Component({ selector: 'ds-browse-by-switcher', - templateUrl: './browse-by-switcher.component.html' + templateUrl: '../../shared/abstract-component-loader/abstract-component-loader.component.html' }) -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ -export class BrowseBySwitcherComponent implements OnInit { +export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { - /** - * Resolved browse-by component - */ - browseByComponent: Observable; + @Input() browseByType: BrowseByDataType; - public constructor(protected route: ActivatedRoute, - protected themeService: ThemeService, - @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor) { - } + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + ...this.inputNamesDependentForComponent, + 'browseByType', + ]; + + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, + 'browseByType', + ]; - /** - * Fetch the correct browse-by component by using the relevant config from the route data - */ - ngOnInit(): void { - this.browseByComponent = this.route.data.pipe( - map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName())) - ); + public getComponent(): GenericConstructor { + return getComponentByBrowseByType(this.browseByType, this.context, this.themeService.getThemeName()); } } diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 3536d92e47b..f3c2b8b6b08 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Inject, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model'; import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; import { ActivatedRoute } from '@angular/router'; -import { Observable, Subscription } from 'rxjs'; +import { Observable } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { BROWSE_BY_COMPONENT_FACTORY, rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; -import { ThemeService } from 'src/app/shared/theme-support/theme.service'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -19,7 +18,7 @@ import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-bro * Component for browsing items by metadata in a hierarchical controlled vocabulary */ @rendersBrowseBy(BrowseByDataType.Hierarchy) -export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { +export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { /** * The {@link VocabularyOptions} object @@ -52,28 +51,23 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { queryParams: any; /** - * Resolved browse-by component + * Resolved browse-by definition */ - browseByComponent: Observable; + browseDefinition$: Observable; - /** - * Subscriptions to track - */ - browseByComponentSubs: Subscription[] = []; - - public constructor( protected route: ActivatedRoute, - protected themeService: ThemeService, - @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor) { + public constructor( + protected route: ActivatedRoute, + ) { + super(); } ngOnInit(): void { - this.browseByComponent = this.route.data.pipe( + this.browseDefinition$ = this.route.data.pipe( map((data: { browseDefinition: BrowseDefinition }) => { - this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName()); return data.browseDefinition; }) ); - this.browseByComponentSubs.push(this.browseByComponent.subscribe((browseDefinition: HierarchicalBrowseDefinition) => { + this.subs.push(this.browseDefinition$.subscribe((browseDefinition: HierarchicalBrowseDefinition) => { this.facetType = browseDefinition.facetType; this.vocabularyName = browseDefinition.vocabulary; this.vocabularyOptions = { name: this.vocabularyName, closed: true }; @@ -113,7 +107,4 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { }; } - ngOnDestroy(): void { - this.browseByComponentSubs.forEach((sub: Subscription) => sub.unsubscribe()); - } } diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index bc7f6a159af..e8d05dc15a7 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -5,12 +5,15 @@ import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse- import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component'; import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component'; import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; -import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; import { FormModule } from '../shared/form/form.module'; +const DECLARATIONS = [ + BrowseBySwitcherComponent, +]; + const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator BrowseByTitlePageComponent, @@ -28,12 +31,12 @@ const ENTRY_COMPONENTS = [ FormModule, ], declarations: [ - BrowseBySwitcherComponent, - ThemedBrowseBySwitcherComponent, + ...DECLARATIONS, ...ENTRY_COMPONENTS ], exports: [ - BrowseBySwitcherComponent + ...DECLARATIONS, + ...ENTRY_COMPONENTS, ] }) export class BrowseByModule { diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts index a5bed53c9fd..8a1ce71b55d 100644 --- a/src/app/core/shared/browse-definition.model.ts +++ b/src/app/core/shared/browse-definition.model.ts @@ -1,5 +1,6 @@ import { autoserialize } from 'cerialize'; import { CacheableObject } from '../cache/cacheable-object.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * Base class for BrowseDefinition models @@ -12,5 +13,5 @@ export abstract class BrowseDefinition extends CacheableObject { /** * Get the render type of the BrowseDefinition model */ - abstract getRenderType(): string; + abstract getRenderType(): BrowseByDataType; } diff --git a/src/app/core/shared/flat-browse-definition.model.ts b/src/app/core/shared/flat-browse-definition.model.ts index 086fca891bb..49e58e1f20a 100644 --- a/src/app/core/shared/flat-browse-definition.model.ts +++ b/src/app/core/shared/flat-browse-definition.model.ts @@ -5,6 +5,7 @@ import { FLAT_BROWSE_DEFINITION } from './flat-browse-definition.resource-type'; import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * BrowseDefinition model for browses of type 'flatBrowse' @@ -30,7 +31,7 @@ export class FlatBrowseDefinition extends NonHierarchicalBrowseDefinition { items: HALLink; }; - getRenderType(): string { + getRenderType(): BrowseByDataType { return this.dataType; } } diff --git a/src/app/core/shared/hierarchical-browse-definition.model.ts b/src/app/core/shared/hierarchical-browse-definition.model.ts index d561fff643f..cf8d7a9ed9a 100644 --- a/src/app/core/shared/hierarchical-browse-definition.model.ts +++ b/src/app/core/shared/hierarchical-browse-definition.model.ts @@ -5,6 +5,7 @@ import { HIERARCHICAL_BROWSE_DEFINITION } from './hierarchical-browse-definition import { HALLink } from './hal-link.model'; import { ResourceType } from './resource-type'; import { BrowseDefinition } from './browse-definition.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * BrowseDefinition model for browses of type 'hierarchicalBrowse' @@ -39,7 +40,7 @@ export class HierarchicalBrowseDefinition extends BrowseDefinition { vocabulary: HALLink; }; - getRenderType(): string { - return 'hierarchy'; + getRenderType(): BrowseByDataType { + return BrowseByDataType.Hierarchy; } } diff --git a/src/app/core/shared/value-list-browse-definition.model.ts b/src/app/core/shared/value-list-browse-definition.model.ts index 3378263962e..e22742bb78d 100644 --- a/src/app/core/shared/value-list-browse-definition.model.ts +++ b/src/app/core/shared/value-list-browse-definition.model.ts @@ -5,6 +5,7 @@ import { VALUE_LIST_BROWSE_DEFINITION } from './value-list-browse-definition.res import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * BrowseDefinition model for browses of type 'valueList' @@ -30,7 +31,7 @@ export class ValueListBrowseDefinition extends NonHierarchicalBrowseDefinition { entries: HALLink; }; - getRenderType(): string { + getRenderType(): BrowseByDataType { return this.dataType; } } diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 46074f7a6b1..12f0f028219 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -125,7 +125,7 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Connect the inputs and outputs of this component to the dynamic component, * to ensure they're in sync */ - protected connectInputsAndOutputs(): void { + public connectInputsAndOutputs(): void { if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { this.inputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { // Using setInput will automatically trigger the ngOnChanges diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index 7b9d010e398..3b97e2a86e3 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -91,7 +91,7 @@ describe('ListableObjectComponentLoaderComponent', () => { listableComponent.reloadedObject.emit(reloadedObject); tick(200); - expect(comp.instantiateComponent).toHaveBeenCalledWith(undefined); + expect(comp.instantiateComponent).toHaveBeenCalledWith(); })); it('should re-emit it as a contentChange', fakeAsync(() => { diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 589effde609..fcbe15cfae9 100644 --- a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-date-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // templateUrl: './browse-by-date-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) -@rendersBrowseBy(BrowseByDataType.Date, 'custom') +@rendersBrowseBy(BrowseByDataType.Date, Context.Any, 'custom') export class BrowseByDatePageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index d2ee7ad6940..533dca29cad 100644 --- a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // templateUrl: './browse-by-metadata-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) -@rendersBrowseBy(BrowseByDataType.Metadata, 'custom') +@rendersBrowseBy(BrowseByDataType.Metadata, Context.Any, 'custom') export class BrowseByMetadataPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.scss b/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 459dc54d7e0..afc0292beff 100644 --- a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // styleUrls: ['./browse-by-taxonomy-page.component.scss'], styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss'], }) -@rendersBrowseBy(BrowseByDataType.Hierarchy, 'custom') +@rendersBrowseBy(BrowseByDataType.Hierarchy, Context.Any, 'custom') export class BrowseByTaxonomyPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 136970b38a0..5424293529b 100644 --- a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-title-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // templateUrl: './browse-by-title-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) -@rendersBrowseBy(BrowseByDataType.Title, 'custom') +@rendersBrowseBy(BrowseByDataType.Title, Context.Any, 'custom') export class BrowseByTitlePageComponent extends BaseComponent { } From e6bf2f0ca71adf02dca64a7a1a07de46c309a8d1 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 12 Dec 2023 00:34:12 +0100 Subject: [PATCH 17/66] Fixed bug in BrowseService where findListByHref was called with null instead of undefined, which prevented its default values from being used --- src/app/core/browse/browse.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index b210b349494..58bbc0b8700 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -105,7 +105,7 @@ export class BrowseService { }) ); if (options.fetchThumbnail ) { - return this.hrefOnlyDataService.findListByHref(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW); + return this.hrefOnlyDataService.findListByHref(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW); } return this.hrefOnlyDataService.findListByHref(href$); } @@ -153,7 +153,7 @@ export class BrowseService { }), ); if (options.fetchThumbnail) { - return this.hrefOnlyDataService.findListByHref(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW); + return this.hrefOnlyDataService.findListByHref(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW); } return this.hrefOnlyDataService.findListByHref(href$); } From a5b7e2a40fc7039789ba5e937ce505b43c27c33a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 12 Dec 2023 01:20:41 +0100 Subject: [PATCH 18/66] Fixed circular dependency by extracting BrowseByDataType to a separate file --- .../browse-by/abstract-browse-by-type.component.ts | 2 +- .../browse-by-date-page.component.ts | 3 ++- src/app/browse-by/browse-by-guard.spec.ts | 2 +- .../browse-by-metadata-page.component.ts | 3 ++- .../browse-by-page.component.spec.ts | 3 ++- .../browse-by-page/browse-by-page.component.ts | 2 +- .../browse-by-switcher/browse-by-data-type.ts | 6 ++++++ .../browse-by-switcher/browse-by-decorator.spec.ts | 3 ++- .../browse-by-switcher/browse-by-decorator.ts | 14 +------------- .../browse-by-switcher.component.spec.ts | 3 ++- .../browse-by-switcher.component.ts | 3 ++- .../browse-by-taxonomy-page.component.ts | 3 ++- .../browse-by-title-page.component.ts | 3 ++- src/app/core/shared/browse-definition.model.ts | 2 +- .../core/shared/flat-browse-definition.model.ts | 2 +- .../shared/hierarchical-browse-definition.model.ts | 2 +- .../shared/non-hierarchical-browse-definition.ts | 2 +- .../shared/value-list-browse-definition.model.ts | 2 +- src/app/navbar/navbar.component.spec.ts | 2 +- .../browse-by-date-page.component.ts | 3 ++- .../browse-by-metadata-page.component.ts | 3 ++- .../browse-by-taxonomy-page.component.ts | 3 ++- .../browse-by-title-page.component.ts | 3 ++- 23 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 src/app/browse-by/browse-by-switcher/browse-by-data-type.ts diff --git a/src/app/browse-by/abstract-browse-by-type.component.ts b/src/app/browse-by/abstract-browse-by-type.component.ts index 246e59a010a..5bd39cfc509 100644 --- a/src/app/browse-by/abstract-browse-by-type.component.ts +++ b/src/app/browse-by/abstract-browse-by-type.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnDestroy } from '@angular/core'; -import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type'; import { Context } from '../core/shared/context.model'; import { Subscription } from 'rxjs'; import { hasValue } from '../shared/empty.util'; diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index b1068033518..2fb9725bdaa 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -19,7 +19,8 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-date-page', diff --git a/src/app/browse-by/browse-by-guard.spec.ts b/src/app/browse-by/browse-by-guard.spec.ts index aac5ba27233..c7d3e1e0c09 100644 --- a/src/app/browse-by/browse-by-guard.spec.ts +++ b/src/app/browse-by/browse-by-guard.spec.ts @@ -2,7 +2,7 @@ import { first } from 'rxjs/operators'; import { BrowseByGuard } from './browse-by-guard'; import { of as observableOf } from 'rxjs'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; -import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type'; import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model'; import { DSONameServiceMock } from '../shared/mocks/dso-name.service.mock'; import { DSONameService } from '../core/breadcrumbs/dso-name.service'; diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 225b9b503c5..4c897b40f8f 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -22,8 +22,9 @@ import { Collection } from '../../core/shared/collection.model'; import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; export const BBM_PAGINATION_ID = 'bbm'; diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts index d60ca02afae..3f9271a67dd 100644 --- a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts @@ -7,11 +7,12 @@ import { ActivatedRoute } from '@angular/router'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { Component } from '@angular/core'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { By } from '@angular/platform-browser'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @rendersBrowseBy('BrowseByPageComponent' as BrowseByDataType) @Component({ diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.ts index ad261334856..9df02562c60 100644 --- a/src/app/browse-by/browse-by-page/browse-by-page.component.ts +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.ts @@ -3,7 +3,7 @@ import { map } from 'rxjs/operators'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; -import { BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-page', diff --git a/src/app/browse-by/browse-by-switcher/browse-by-data-type.ts b/src/app/browse-by/browse-by-switcher/browse-by-data-type.ts new file mode 100644 index 00000000000..5324018b346 --- /dev/null +++ b/src/app/browse-by/browse-by-switcher/browse-by-data-type.ts @@ -0,0 +1,6 @@ +export enum BrowseByDataType { + Title = 'title', + Metadata = 'text', + Date = 'date', + Hierarchy = 'hierarchy', +} diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts index 19a6277151c..64604cdc04a 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts @@ -1,4 +1,5 @@ -import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator'; +import { BrowseByDataType } from './browse-by-data-type'; +import { rendersBrowseBy } from './browse-by-decorator'; describe('BrowseByDecorator', () => { const titleDecorator = rendersBrowseBy(BrowseByDataType.Title); diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index 6891fba0036..835c34ebcbc 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -1,25 +1,13 @@ import { hasNoValue } from '../../shared/empty.util'; -import { InjectionToken } from '@angular/core'; import { DEFAULT_THEME, resolveTheme } from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; - -export enum BrowseByDataType { - Title = 'title', - Metadata = 'text', - Date = 'date', - Hierarchy = 'hierarchy', -} +import { BrowseByDataType } from './browse-by-data-type'; export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any; -export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType: BrowseByDataType, context: Context, theme: string) => GenericConstructor>('getComponentByBrowseByType', { - providedIn: 'root', - factory: () => getComponentByBrowseByType -}); - const map: Map>>> = new Map(); /** diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts index 5812a54269b..418dfd45e15 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts @@ -1,7 +1,7 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SimpleChange, Component } from '@angular/core'; -import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator'; +import { rendersBrowseBy } from './browse-by-decorator'; import { ThemeService } from '../../shared/theme-support/theme.service'; import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model'; import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model'; @@ -9,6 +9,7 @@ import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchi import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from './browse-by-data-type'; @rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType) @Component({ diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 881df713112..2ebea9a262e 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -1,8 +1,9 @@ import { Component, Input } from '@angular/core'; -import { getComponentByBrowseByType, BrowseByDataType } from './browse-by-decorator'; +import { getComponentByBrowseByType } from './browse-by-decorator'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from './browse-by-data-type'; @Component({ selector: 'ds-browse-by-switcher', diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index f3c2b8b6b08..f005c66c9e0 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -4,10 +4,11 @@ import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-taxonomy-page', diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index cec30712da1..1e18429fa0a 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -13,7 +13,8 @@ import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-title-page', diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts index 8a1ce71b55d..72239b26f75 100644 --- a/src/app/core/shared/browse-definition.model.ts +++ b/src/app/core/shared/browse-definition.model.ts @@ -1,6 +1,6 @@ import { autoserialize } from 'cerialize'; import { CacheableObject } from '../cache/cacheable-object.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * Base class for BrowseDefinition models diff --git a/src/app/core/shared/flat-browse-definition.model.ts b/src/app/core/shared/flat-browse-definition.model.ts index 49e58e1f20a..9f37f1c422c 100644 --- a/src/app/core/shared/flat-browse-definition.model.ts +++ b/src/app/core/shared/flat-browse-definition.model.ts @@ -5,7 +5,7 @@ import { FLAT_BROWSE_DEFINITION } from './flat-browse-definition.resource-type'; import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * BrowseDefinition model for browses of type 'flatBrowse' diff --git a/src/app/core/shared/hierarchical-browse-definition.model.ts b/src/app/core/shared/hierarchical-browse-definition.model.ts index cf8d7a9ed9a..2410bf7b7a4 100644 --- a/src/app/core/shared/hierarchical-browse-definition.model.ts +++ b/src/app/core/shared/hierarchical-browse-definition.model.ts @@ -5,7 +5,7 @@ import { HIERARCHICAL_BROWSE_DEFINITION } from './hierarchical-browse-definition import { HALLink } from './hal-link.model'; import { ResourceType } from './resource-type'; import { BrowseDefinition } from './browse-definition.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * BrowseDefinition model for browses of type 'hierarchicalBrowse' diff --git a/src/app/core/shared/non-hierarchical-browse-definition.ts b/src/app/core/shared/non-hierarchical-browse-definition.ts index d5481fcc8d0..e3319affdb9 100644 --- a/src/app/core/shared/non-hierarchical-browse-definition.ts +++ b/src/app/core/shared/non-hierarchical-browse-definition.ts @@ -1,6 +1,6 @@ import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; import { SortOption } from './sort-option.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; import { BrowseDefinition } from './browse-definition.model'; /** diff --git a/src/app/core/shared/value-list-browse-definition.model.ts b/src/app/core/shared/value-list-browse-definition.model.ts index e22742bb78d..0302ec59c73 100644 --- a/src/app/core/shared/value-list-browse-definition.model.ts +++ b/src/app/core/shared/value-list-browse-definition.model.ts @@ -5,7 +5,7 @@ import { VALUE_LIST_BROWSE_DEFINITION } from './value-list-browse-definition.res import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * BrowseDefinition model for browses of type 'valueList' diff --git a/src/app/navbar/navbar.component.spec.ts b/src/app/navbar/navbar.component.spec.ts index 983eace0557..db1c4484093 100644 --- a/src/app/navbar/navbar.component.spec.ts +++ b/src/app/navbar/navbar.component.spec.ts @@ -16,7 +16,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { BrowseService } from '../core/browse/browse.service'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { buildPaginatedList } from '../core/data/paginated-list.model'; -import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-data-type'; import { Item } from '../core/shared/item.model'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { ThemeService } from '../shared/theme-support/theme.service'; diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index fcbe15cfae9..58a38c41e57 100644 --- a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 533dca29cad..ef57478087d 100644 --- a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index afc0292beff..b8fb968c762 100644 --- a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 5424293529b..4f6a1a50207 100644 --- a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ From 7942c900f4697368e1583eeab0ba6b5f73be3b9e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 17 Dec 2023 16:17:16 +0100 Subject: [PATCH 19/66] Component doesn't have to be initialised anymore in ngOnChanges because connectInputsAndOutputs will automatically call the child component's ngOnChanges already --- .../abstract-component-loader.component.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 12f0f028219..888edffe675 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -2,7 +2,7 @@ import { Component, ComponentRef, Input, OnChanges, OnDestroy, OnInit, SimpleCha import { Context } from '../../core/shared/context.model'; import { ThemeService } from '../theme-support/theme.service'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isNotEmpty } from '../empty.util'; import { Subscription } from 'rxjs'; import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive'; @@ -57,21 +57,15 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Set up the dynamic child component */ ngOnInit(): void { - if (hasNoValue(this.compRef)) { - this.instantiateComponent(); - } + this.instantiateComponent(); } /** * Whenever the inputs change, update the inputs of the dynamic component */ ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(); - } else { - if (this.inputNamesDependentForComponent.some((name: any) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { + if (hasValue(this.compRef)) { + if (this.inputNamesDependentForComponent.some((name: keyof this & string) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { // Recreate the component when the @Input()s used by getComponent() aren't up-to-date anymore this.destroyComponentInstance(); this.instantiateComponent(); @@ -123,7 +117,7 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC /** * Connect the inputs and outputs of this component to the dynamic component, - * to ensure they're in sync + * to ensure they're in sync, the ngOnChanges method will automatically be called by setInput */ public connectInputsAndOutputs(): void { if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { From 480c7a6ce0664e3ca3d3a096dff45e59e8519e87 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 26 Dec 2023 14:31:07 +0100 Subject: [PATCH 20/66] Destroy dynamically generated components in onDestroy & replace deprecated createComponent --- ...in-search-result-grid-element.component.ts | 43 +++++++++++------- ...t-admin-workflow-grid-element.component.ts | 45 ++++++++++++------- .../loading/themed-loading.component.ts | 5 +-- ...etadata-representation-loader.component.ts | 19 +++++--- .../claimed-task-actions-loader.component.ts | 16 +++---- ...table-object-component-loader.component.ts | 12 +++-- .../shared/theme-support/themed.component.ts | 5 +-- 7 files changed, 88 insertions(+), 57 deletions(-) diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index 1ab8fee8c29..06970661d51 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild, ComponentRef, OnDestroy } from '@angular/core'; import { Item } from '../../../../../core/shared/item.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { @@ -13,6 +13,7 @@ import { BitstreamDataService } from '../../../../../core/data/bitstream-data.se import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { hasValue } from '../../../../../shared/empty.util'; @listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch) @Component({ @@ -23,15 +24,16 @@ import { ThemeService } from '../../../../../shared/theme-support/theme.service' /** * The component for displaying a list element for an item search result on the admin search page */ -export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { +export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnDestroy, OnInit { @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; @ViewChild('badges', { static: true }) badges: ElementRef; @ViewChild('buttons', { static: true }) buttons: ElementRef; + protected compRef: ComponentRef; + constructor(protected truncatableService: TruncatableService, protected bitstreamDataService: BitstreamDataService, private themeService: ThemeService, - private componentFactoryResolver: ComponentFactoryResolver ) { super(truncatableService, bitstreamDataService); } @@ -41,23 +43,32 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE */ ngOnInit(): void { super.ngOnInit(); - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + const component: GenericConstructor = this.getComponent(); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent( - componentFactory, - 0, - undefined, - [ - [this.badges.nativeElement], - [this.buttons.nativeElement] - ]); - (componentRef.instance as any).object = this.object; - (componentRef.instance as any).index = this.index; - (componentRef.instance as any).linkType = this.linkType; - (componentRef.instance as any).listID = this.listID; + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + (this.compRef.instance as any).object = this.object; + (this.compRef.instance as any).index = this.index; + (this.compRef.instance as any).linkType = this.linkType; + (this.compRef.instance as any).listID = this.listID; + } + + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } } /** diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts index 68f10916d55..14d6e81aca3 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, ElementRef, ViewChild } from '@angular/core'; +import { Component, ElementRef, ViewChild, ComponentRef, OnDestroy, OnInit } from '@angular/core'; import { Item } from '../../../../../core/shared/item.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { @@ -23,6 +23,7 @@ import { import { take } from 'rxjs/operators'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { hasValue } from '../../../../../shared/empty.util'; @listableObjectComponent(WorkflowItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch) @Component({ @@ -33,7 +34,7 @@ import { ThemeService } from '../../../../../shared/theme-support/theme.service' /** * The component for displaying a grid element for an workflow item on the admin workflow search page */ -export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent { +export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent implements OnDestroy, OnInit { /** * Directive used to render the dynamic component in */ @@ -54,8 +55,9 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S */ public item$: Observable; + protected compRef: ComponentRef; + constructor( - private componentFactoryResolver: ComponentFactoryResolver, private linkService: LinkService, protected truncatableService: TruncatableService, private themeService: ThemeService, @@ -73,28 +75,37 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S this.dso = this.linkService.resolveLink(this.dso, followLink('item')); this.item$ = (this.dso.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload()); this.item$.pipe(take(1)).subscribe((item: Item) => { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); + const component: GenericConstructor = this.getComponent(item); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent( - componentFactory, - 0, - undefined, - [ - [this.badges.nativeElement], - [this.buttons.nativeElement] - ]); - (componentRef.instance as any).object = item; - (componentRef.instance as any).index = this.index; - (componentRef.instance as any).linkType = this.linkType; - (componentRef.instance as any).listID = this.listID; - componentRef.changeDetectorRef.detectChanges(); + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + (this.compRef.instance as any).object = item; + (this.compRef.instance as any).index = this.index; + (this.compRef.instance as any).linkType = this.linkType; + (this.compRef.instance as any).listID = this.listID; + this.compRef.changeDetectorRef.detectChanges(); } ); } + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + /** * Fetch the component depending on the item's entity type, view mode and context * @returns {GenericConstructor} diff --git a/src/app/shared/loading/themed-loading.component.ts b/src/app/shared/loading/themed-loading.component.ts index ffdf9d3cbe6..5b45cff9045 100644 --- a/src/app/shared/loading/themed-loading.component.ts +++ b/src/app/shared/loading/themed-loading.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, ComponentFactoryResolver, ChangeDetectorRef } from '@angular/core'; +import { Component, Input, ChangeDetectorRef } from '@angular/core'; import { ThemedComponent } from '../theme-support/themed.component'; import { LoadingComponent } from './loading.component'; import { ThemeService } from '../theme-support/theme.service'; @@ -20,11 +20,10 @@ export class ThemedLoadingComponent extends ThemedComponent { protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage', 'spinner']; constructor( - protected resolver: ComponentFactoryResolver, protected cdr: ChangeDetectorRef, protected themeService: ThemeService ) { - super(resolver, cdr, themeService); + super(cdr, themeService); } protected getComponentName(): string { diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 70779498094..ddb764ed151 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, Input, OnInit, ViewChild, ComponentRef, OnDestroy } from '@angular/core'; import { MetadataRepresentation, MetadataRepresentationType @@ -19,8 +19,9 @@ import { ThemeService } from '../theme-support/theme.service'; /** * Component for determining what component to use depending on the item's entity type (dspace.entity.type), its metadata representation and, optionally, its context */ -export class MetadataRepresentationLoaderComponent implements OnInit { +export class MetadataRepresentationLoaderComponent implements OnDestroy, OnInit { private componentRefInstance: MetadataRepresentationListElementComponent; + protected compRef: ComponentRef; /** * The item or metadata to determine the component for @@ -47,7 +48,6 @@ export class MetadataRepresentationLoaderComponent implements OnInit { @ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective; constructor( - private componentFactoryResolver: ComponentFactoryResolver, private themeService: ThemeService, @Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor, ) { @@ -57,16 +57,23 @@ export class MetadataRepresentationLoaderComponent implements OnInit { * Set up the dynamic child component */ ngOnInit(): void { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + const component: GenericConstructor = this.getComponent(); const viewContainerRef = this.mdRepDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent(componentFactory); - this.componentRefInstance = componentRef.instance as MetadataRepresentationListElementComponent; + this.compRef = viewContainerRef.createComponent(component); + this.componentRefInstance = this.compRef.instance as MetadataRepresentationListElementComponent; this.componentRefInstance.metadataRepresentation = this.mdRepresentation; } + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + /** * Fetch the component depending on the item's entity type, metadata representation type and context * @returns {string} diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index 6a14aeb5bc8..e835c1b1da9 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,12 +1,11 @@ import { Component, - ComponentFactoryResolver, EventEmitter, Input, OnDestroy, OnInit, Output, - ViewChild + ViewChild, ComponentRef } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; @@ -64,8 +63,7 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { */ protected subs: Subscription[] = []; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { - } + protected compRef: ComponentRef; /** * Fetch, create and initialize the relevant component @@ -74,13 +72,11 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { const comp = this.getComponentByWorkflowTaskOption(this.option); if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent(componentFactory); - const componentInstance = (componentRef.instance as ClaimedTaskActionsAbstractComponent); + this.compRef = viewContainerRef.createComponent(comp); + const componentInstance = (this.compRef.instance as ClaimedTaskActionsAbstractComponent); componentInstance.item = this.item; componentInstance.object = this.object; componentInstance.workflowitem = this.workflowitem; @@ -98,6 +94,10 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { * Unsubscribe from open subscriptions */ ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 6b75c591816..747a32cfec5 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -23,7 +23,7 @@ import { getListableObjectComponent } from './listable-object.decorator'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { ListableObjectDirective } from './listable-object.directive'; import { CollectionElementLinkType } from '../../collection-element-link.type'; -import { hasValue, isNotEmpty } from '../../../empty.util'; +import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { ThemeService } from '../../../theme-support/theme.service'; @@ -141,7 +141,9 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges * Setup the dynamic child component */ ngOnInit(): void { - this.instantiateComponent(this.object); + if (hasNoValue(this.compRef)) { + this.instantiateComponent(this.object); + } } /** @@ -153,7 +155,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges } } - ngOnDestroy() { + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 87f182a5ffb..2b75f5429c1 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -6,7 +6,6 @@ import { SimpleChanges, OnInit, OnDestroy, - ComponentFactoryResolver, ChangeDetectorRef, OnChanges } from '@angular/core'; @@ -31,7 +30,6 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges protected inAndOutputNames: (keyof T & keyof this)[] = []; constructor( - protected resolver: ComponentFactoryResolver, protected cdr: ChangeDetectorRef, protected themeService: ThemeService ) { @@ -87,8 +85,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges } }), ).subscribe((constructor: GenericConstructor) => { - const factory = this.resolver.resolveComponentFactory(constructor); - this.compRef = this.vcr.createComponent(factory); + this.compRef = this.vcr.createComponent(constructor); this.connectInputsAndOutputs(); this.cdr.markForCheck(); }); From 2f5370a08569045239fc73076552d70dfcffe6d8 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 16 Dec 2023 00:55:46 +0100 Subject: [PATCH 21/66] Fixed accessibility issues related to dso selectors - Fixed header ordering - Fixed input field not having a description what it does (because the header isn't always shown I decided to use aria-labels instead of regular labels) --- .../dso-selector/dso-selector/dso-selector.component.html | 1 + .../dso-selector/dso-selector/dso-selector.component.ts | 5 +++++ .../create-community-parent-selector.component.html | 6 +++--- .../create-item-parent-selector.component.html | 2 +- .../dso-selector-modal-wrapper.component.html | 2 +- .../edit-item-selector/edit-item-selector.component.html | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html index c4f5dbc4cd6..295165d171f 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html @@ -2,6 +2,7 @@
diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts index 503e4c44129..4590e05363d 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts @@ -75,6 +75,11 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { */ @Input() sort: SortOptions; + /** + * The id that should be given to the input box, this is required for accessibility reasons + */ + @Input() searchBoxId: string | null = null; + // list of allowed selectable dsoTypes typesString: string; diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html index a13be638803..8ae146b42c4 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.html @@ -6,14 +6,14 @@
-
{{'dso-selector.create.community.sub-level' | translate}}
+ {{'dso-selector.create.community.sub-level' | translate}}
diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html index 5fccd58f48c..5288f08e02b 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html @@ -5,7 +5,7 @@
diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html index 85d8797e660..999a96e7301 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html @@ -5,7 +5,7 @@ From 086c5463a8c1e84145139dd2ad084749d275a62a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 16 Dec 2023 00:56:30 +0100 Subject: [PATCH 22/66] Removed invisible buttons on the CommunityListComponent & fixed content displacement when expanding com/col --- .../community-list.component.html | 46 +++++++++---------- .../community-list.component.scss | 4 ++ .../community-list.component.spec.ts | 35 +++++++------- .../community-list.component.ts | 1 + src/assets/i18n/en.json5 | 4 ++ 5 files changed, 51 insertions(+), 39 deletions(-) create mode 100644 src/app/community-list-page/community-list/community-list.component.scss diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index de67607bb4b..5e3d6c5fa53 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -4,9 +4,9 @@
- +
+ +
@@ -46,10 +49,9 @@
- + {{node.payload.shortDescription}} @@ -58,10 +60,9 @@
- +
@@ -69,9 +70,9 @@
- +
{{ dsoNameService.getName(node.payload) }} @@ -81,10 +82,9 @@
- + {{node.payload.shortDescription}} diff --git a/src/app/community-list-page/community-list/community-list.component.scss b/src/app/community-list-page/community-list/community-list.component.scss new file mode 100644 index 00000000000..2e33380a29f --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.scss @@ -0,0 +1,4 @@ +::ng-deep .fa-chevron-right::before { + display: block; + width: 16px; +} diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index fb47f4994d2..cec1b555ab8 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -5,7 +5,7 @@ import { CommunityListService, showMoreFlatNode, toFlatNode } from '../community import { CdkTreeModule } from '@angular/cdk/tree'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core'; import { RouterTestingModule } from '@angular/router/testing'; import { Community } from '../../core/shared/community.model'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; @@ -300,12 +300,14 @@ describe('CommunityListComponent', () => { describe('second top community node is expanded and has more children (collections) than page size of collection', () => { describe('children of second top com are added (page-limited pageSize 2)', () => { - let allNodes; + let allNodes: DebugElement[]; beforeEach(fakeAsync(() => { - const chevronExpand = fixture.debugElement.queryAll(By.css('.expandable-node button')); - const chevronExpandSpan = fixture.debugElement.queryAll(By.css('.expandable-node button span')); - if (chevronExpandSpan[1].nativeElement.classList.contains('fa-chevron-right')) { - chevronExpand[1].nativeElement.click(); + const toggleButtons: DebugElement[] = fixture.debugElement.queryAll(By.css('.expandable-node button')); + const toggleButtonText: DebugElement = toggleButtons[1].query(By.css('span')); + expect(toggleButtonText).not.toBeNull(); + + if (toggleButtonText.nativeElement.classList.contains('fa-chevron-right')) { + toggleButtons[1].nativeElement.click(); tick(); fixture.detectChanges(); } @@ -315,17 +317,18 @@ describe('CommunityListComponent', () => { allNodes = [...expandableNodesFound, ...childlessNodesFound]; })); it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => { - mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { - expect(allNodes.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); - })).toBeTruthy(); - }); - mockCollectionsPage1.map((coll) => { - expect(allNodes.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === coll.name); - })).toBeTruthy(); - }); + const allNodeNames: string[] = allNodes.map((node: DebugElement) => node.nativeElement.innerText.trim()); expect(allNodes.length).toEqual(4); + const flatNodes: string[] = mockTopFlatnodesUnexpanded.slice(0, 2).map((flatNode: FlatNode) => flatNode.name); + for (const flatNode of flatNodes) { + expect(allNodeNames).toContain(flatNode); + } + expect(flatNodes.length).toBe(2); + const page1CollectionNames: string[] = mockCollectionsPage1.map((collection: Collection) => collection.name); + for (const collectionName of page1CollectionNames) { + expect(allNodeNames).toContain(collectionName); + } + expect(page1CollectionNames.length).toBe(2); const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); expect(showMoreEl.length).toEqual(2); }); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 6b5c6578e1f..031a4224692 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -19,6 +19,7 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; @Component({ selector: 'ds-community-list', templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.scss'], }) export class CommunityListComponent implements OnInit, OnDestroy { diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index d7c50b1a603..4f25ae0d0fa 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1168,6 +1168,10 @@ "communityList.showMore": "Show More", + "communityList.expand": "Expand {{ name }}", + + "communityList.collapse": "Collapse {{ name }}", + "community.create.head": "Create a Community", "community.create.notifications.success": "Successfully created the Community", From 3c1243f6f183d12fa39a15984077086f41c264fc Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 16 Dec 2023 01:31:34 +0100 Subject: [PATCH 23/66] Fixed search filters having empty input buttons --- .../org-unit-input-suggestions.component.html | 4 +++- .../org-unit-input-suggestions.component.spec.ts | 2 ++ .../person-input-suggestions.component.html | 4 +++- .../dso-input-suggestions.component.html | 4 +++- .../filter-input-suggestions.component.html | 7 +++---- .../input-suggestions/input-suggestions.component.html | 4 +++- .../validation-suggestions.component.html | 4 +++- .../search-range-filter/search-range-filter.component.html | 7 +++---- 8 files changed, 23 insertions(+), 13 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 c4e31d3d81d..58f39b8d0b8 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 @@ -11,7 +11,9 @@ [dsDebounce]="debounceTime" (onDebounce)="find($event)" [placeholder]="placeholder" [ngModelOptions]="{standalone: true}" autocomplete="off"/> - +