From a9c5c20c5aa810d516eb28d87ee1f217336fe90a Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 16 Jun 2023 11:45:26 +0200 Subject: [PATCH 01/30] 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/30] 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 98241d8925cbc394c78feb124cdea476c67da4be Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 21:54:32 +0300 Subject: [PATCH 03/30] 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 04/30] 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 05/30] 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 06/30] 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 07/30] 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 08/30] 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 09/30] 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 10/30] 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 11/30] 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 12/30] 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 13/30] 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 14/30] 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 15/30] 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 16/30] 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 17/30] 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 18/30] 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 e77898eaacb3489f9bdd077a709c90ca66ca0dec Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Tue, 23 Jan 2024 21:47:29 +0000 Subject: [PATCH 19/30] Update item-status.component.ts Fix initialOperations typo and return ops with register doi op --- .../edit-item-page/item-status/item-status.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 8e04985c184..d3a5e033d71 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -117,7 +117,7 @@ export class ItemStatusComponent implements OnInit { * The value is supposed to be a href for the button */ const currentUrl = this.getCurrentUrl(item); - const inititalOperations: ItemOperation[] = [ + const initialOperations: ItemOperation[] = [ new ItemOperation('authorizations', `${currentUrl}/authorizations`, FeatureID.CanManagePolicies, true), new ItemOperation('mappedCollections', `${currentUrl}/mapper`, FeatureID.CanManageMappings, true), item.isWithdrawn @@ -130,7 +130,7 @@ export class ItemStatusComponent implements OnInit { new ItemOperation('delete', `${currentUrl}/delete`, FeatureID.CanDelete, true) ]; - this.operations$.next(inititalOperations); + this.operations$.next(initialOperations); /** * When the identifier data stream changes, determine whether the register DOI button should be shown or not. @@ -170,12 +170,12 @@ export class ItemStatusComponent implements OnInit { }), // Switch map pushes the register DOI operation onto a copy of the base array then returns to the pipe switchMap((showDoi: boolean) => { - const ops = [...inititalOperations]; + const ops = [...initialOperations]; if (showDoi) { const op = new ItemOperation('register-doi', `${currentUrl}/register-doi`, FeatureID.CanRegisterDOI, true); ops.splice(ops.length - 1, 0, op); // Add item before last } - return inititalOperations; + return ops; }), concatMap((op: ItemOperation) => { if (hasValue(op.featureID)) { From 6497a156aa1888da2de48dae235e67ae93d49fe9 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 28 Dec 2023 20:42:21 +0100 Subject: [PATCH 20/30] Destroy dynamically generated components in onDestroy & replace deprecated createComponent --- ...in-search-result-grid-element.component.ts | 8 ++-- ...t-admin-workflow-grid-element.component.ts | 37 ++++++++-------- ...t-admin-workflow-grid-element.component.ts | 42 ++++++++++++------- src/app/shared/context-help.directive.ts | 21 +++++----- ...anced-workflow-actions-loader.component.ts | 24 ++++++++--- 5 files changed, 78 insertions(+), 54 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 481145ab8bb..36707187dd3 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 @@ -61,10 +61,10 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE ], }, ); - (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; + this.compRef.setInput('object',this.object); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); } ngOnDestroy(): void { 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 0c4527070d5..4c25a9f9983 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 @@ -77,28 +77,27 @@ 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 component: GenericConstructor = this.getComponent(item); + const component: GenericConstructor = this.getComponent(item); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; - viewContainerRef.clear(); + const viewContainerRef = this.listableObjectDirective.viewContainerRef; + viewContainerRef.clear(); - 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 = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + this.compRef.setInput('object', item); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); this.compRef.changeDetectorRef.detectChanges(); - } - ); + }); } ngOnDestroy(): void { 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..9404b02a2a5 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 @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, ElementRef, ViewChild, OnInit } from '@angular/core'; +import { Component, ElementRef, ViewChild, OnInit, OnDestroy, ComponentRef } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { map, mergeMap, take, tap } from 'rxjs/operators'; @@ -37,6 +37,7 @@ import { SupervisionOrder } from '../../../../../core/supervision-order/models/s import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { hasValue } from '../../../../../shared/empty.util'; @listableObjectComponent(WorkspaceItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch) @Component({ @@ -47,7 +48,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service /** * The component for displaying a grid element for an workflow item on the admin workflow search page */ -export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent implements OnInit { +export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent implements OnDestroy, OnInit { /** * The item linked to the workspace item @@ -79,9 +80,13 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends */ @ViewChild('buttons', { static: true }) buttons: ElementRef; + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + constructor( public dsoNameService: DSONameService, - private componentFactoryResolver: ComponentFactoryResolver, private linkService: LinkService, protected truncatableService: TruncatableService, private themeService: ThemeService, @@ -100,24 +105,24 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends 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.compRef = viewContainerRef.createComponent( + component, { + index: 0, + projectableNodes: [ [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.setInput('object', item); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); + this.compRef.changeDetectorRef.detectChanges(); } ); @@ -130,6 +135,13 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends }); } + 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/context-help.directive.ts b/src/app/shared/context-help.directive.ts index 41d6daec21c..52a822de2c4 100644 --- a/src/app/shared/context-help.directive.ts +++ b/src/app/shared/context-help.directive.ts @@ -1,5 +1,4 @@ import { - ComponentFactoryResolver, ComponentRef, Directive, Input, @@ -12,6 +11,7 @@ import { PlacementArray } from '@ng-bootstrap/ng-bootstrap/util/positioning'; import { ContextHelpWrapperComponent } from './context-help-wrapper/context-help-wrapper.component'; import { PlacementDir } from './context-help-wrapper/placement-dir.model'; import { ContextHelpService } from './context-help.service'; +import { hasValue } from './empty.util'; export interface ContextHelpDirectiveInput { content: string; @@ -43,7 +43,6 @@ export class ContextHelpDirective implements OnChanges, OnDestroy { constructor( private templateRef: TemplateRef, private viewContainerRef: ViewContainerRef, - private componentFactoryResolver: ComponentFactoryResolver, private contextHelpService: ContextHelpService ) {} @@ -53,19 +52,21 @@ export class ContextHelpDirective implements OnChanges, OnDestroy { this.contextHelpService.add({id: this.dsContextHelp.id, isTooltipVisible: false}); if (this.wrapper === undefined) { - const factory - = this.componentFactoryResolver.resolveComponentFactory(ContextHelpWrapperComponent); - this.wrapper = this.viewContainerRef.createComponent(factory); + this.wrapper = this.viewContainerRef.createComponent(ContextHelpWrapperComponent); } - this.wrapper.instance.templateRef = this.templateRef; - this.wrapper.instance.content = this.dsContextHelp.content; - this.wrapper.instance.id = this.dsContextHelp.id; - this.wrapper.instance.tooltipPlacement = this.dsContextHelp.tooltipPlacement; - this.wrapper.instance.iconPlacement = this.dsContextHelp.iconPlacement; + this.wrapper.setInput('templateRef', this.templateRef); + this.wrapper.setInput('content', this.dsContextHelp.content); + this.wrapper.setInput('id', this.dsContextHelp.id); + this.wrapper.setInput('tooltipPlacement', this.dsContextHelp.tooltipPlacement); + this.wrapper.setInput('iconPlacement', this.dsContextHelp.iconPlacement); } ngOnDestroy() { this.clearMostRecentId(); + if (hasValue(this.wrapper)) { + this.wrapper.destroy(); + this.wrapper = undefined; + } } private clearMostRecentId(): void { 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..db47c6638ff 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,4 +1,4 @@ -import { Component, Input, ViewChild, ComponentFactoryResolver, OnInit } from '@angular/core'; +import { Component, Input, ViewChild, OnInit, ComponentRef, OnDestroy } from '@angular/core'; import { hasValue } from '../../../shared/empty.util'; import { getAdvancedComponentByWorkflowTaskOption @@ -15,7 +15,7 @@ import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; templateUrl: './advanced-workflow-actions-loader.component.html', styleUrls: ['./advanced-workflow-actions-loader.component.scss'], }) -export class AdvancedWorkflowActionsLoaderComponent implements OnInit { +export class AdvancedWorkflowActionsLoaderComponent implements OnDestroy, OnInit { /** * The name of the type to render @@ -28,8 +28,12 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { */ @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective; + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + constructor( - private componentFactoryResolver: ComponentFactoryResolver, private router: Router, ) { } @@ -40,16 +44,24 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { 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); + this.compRef = viewContainerRef.createComponent(comp); } else { void this.router.navigate([PAGE_NOT_FOUND_PATH]); } } + /** + * Destroy the dynamically created component + */ + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + getComponentByWorkflowTaskOption(type: string): any { return getAdvancedComponentByWorkflowTaskOption(type); } From 2c2f59c7ef39a89aed02711f14098a0e4bb533dd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 14:27:33 -0600 Subject: [PATCH 21/30] Update cli.yml to reference dspacenet in docker-compose-rest. Remove unnecessary network from cli.assetstore.yml --- docker/cli.assetstore.yml | 5 ----- docker/cli.yml | 15 ++++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docker/cli.assetstore.yml b/docker/cli.assetstore.yml index 40e4974c7c7..31bc53f64d8 100644 --- a/docker/cli.assetstore.yml +++ b/docker/cli.assetstore.yml @@ -14,13 +14,8 @@ # Therefore, it should be kept in sync with that file version: "3.7" -networks: - dspacenet: - services: dspace-cli: - networks: - dspacenet: {} environment: # This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz diff --git a/docker/cli.yml b/docker/cli.yml index 223ec356b9c..c2664fdf3e1 100644 --- a/docker/cli.yml +++ b/docker/cli.yml @@ -13,7 +13,12 @@ # # Therefore, it should be kept in sync with that file version: "3.7" - +networks: + # Default to using network named 'dspacenet' from docker-compose-rest.yml. + # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + default: + name: ${COMPOSE_PROJECT_NAME}_dspacenet + external: true services: dspace-cli: image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}" @@ -30,16 +35,12 @@ services: # solr.server: Ensure we are using the 'dspacesolr' image for Solr solr__P__server: http://dspacesolr:8983/solr volumes: - - "assetstore:/dspace/assetstore" + # Keep DSpace assetstore directory between reboots + - assetstore:/dspace/assetstore entrypoint: /dspace/bin/dspace command: help - networks: - - dspacenet tty: true stdin_open: true volumes: assetstore: - -networks: - dspacenet: From 239f082cce32edce7719ba3eab8f71e06e3955d0 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 2 Feb 2024 09:35:27 +0100 Subject: [PATCH 22/30] Moved the context @Input() to the loaders (since they are not always required for all the loaders) & added AbstractComponentLoaderComponent documentation --- .../browse-by-switcher.component.ts | 7 +++- .../abstract-component-loader.component.ts | 40 +++++++++++-------- ...etadata-representation-loader.component.ts | 9 ++++- .../claimed-task-actions-loader.component.ts | 2 - ...table-object-component-loader.component.ts | 11 +++-- 5 files changed, 44 insertions(+), 25 deletions(-) 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 2ebea9a262e..99ad720dd3d 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 @@ -4,6 +4,7 @@ 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'; +import { Context } from '../../core/shared/context.model'; @Component({ selector: 'ds-browse-by-switcher', @@ -11,15 +12,17 @@ import { BrowseByDataType } from './browse-by-data-type'; }) export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { + @Input() context: Context; + @Input() browseByType: BrowseByDataType; protected inputNamesDependentForComponent: (keyof this & string)[] = [ - ...this.inputNamesDependentForComponent, + 'context', 'browseByType', ]; protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, + 'context', 'browseByType', ]; 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 888edffe675..2ea7c50d752 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 @@ -1,22 +1,27 @@ -import { Component, ComponentRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; -import { Context } from '../../core/shared/context.model'; +import { Component, ComponentRef, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; import { ThemeService } from '../theme-support/theme.service'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { hasValue, isNotEmpty } from '../empty.util'; import { Subscription } from 'rxjs'; import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive'; +/** + * To create a new loader component you will need to: + *
    + *
  • Create a new LoaderComponent component extending this component
  • + *
  • Point the templateUrl to this component's templateUrl
  • + *
  • Add all the @Input()/@Output() names that the dynamically generated components should inherit from the loader to the inputNames/outputNames lists
  • + *
  • Create a decorator file containing the new decorator function, a map containing all the collected {@link Component}s and a function to retrieve the {@link Component}
  • + *
  • Call the function to retrieve the correct {@link Component} in getComponent()
  • + *
  • Add all the @Input()s you had to used in getComponent() in the inputNamesDependentForComponent array
  • + *
+ */ @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 */ @@ -33,20 +38,21 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC protected subs: Subscription[] = []; /** - * 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 + * The @Input() that are used to find the matching component using {@link getComponent}. When the value of + * one of these @Input() change this loader needs to retrieve the best matching component again using the * {@link getComponent} method. */ - protected inputNamesDependentForComponent: (keyof this & string)[] = [ - 'context', - ]; + protected inputNamesDependentForComponent: (keyof this & string)[] = []; - protected inputNames: (keyof this & string)[] = [ - 'context', - ]; + /** + * The list of the @Input() names that should be passed down to the dynamically created components. + */ + protected inputNames: (keyof this & string)[] = []; - protected outputNames: (keyof this & string)[] = [ - ]; + /** + * The list of the @Output() names that should be passed down to the dynamically created components. + */ + protected outputNames: (keyof this & string)[] = []; constructor( protected themeService: ThemeService, 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 4b63a31a2b4..d722d24ff67 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -19,13 +19,20 @@ import { AbstractComponentLoaderComponent } from '../abstract-component-loader/a */ export class MetadataRepresentationLoaderComponent extends AbstractComponentLoaderComponent { + @Input() context: Context; + /** * The item or metadata to determine the component for */ @Input() mdRepresentation: MetadataRepresentation; + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + 'context', + 'mdRepresentation', + ]; + protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, + 'context', '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 fb39347d63c..7b01e982495 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 @@ -47,7 +47,6 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo * The list of input and output names for the dynamic component */ protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, 'item', 'object', 'option', @@ -55,7 +54,6 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo ]; 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 a83cdfb6b2a..d2807686310 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 @@ -58,7 +58,7 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa /** * Whether to show the thumbnail preview */ - @Input() showThumbnails; + @Input() showThumbnails: boolean; /** * The value to display for this element @@ -70,13 +70,19 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa */ @Output() contentChange = new EventEmitter(); + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + 'object', + 'viewMode', + 'context', + ]; + /** * The list of input and output names for the dynamic component */ protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, 'object', 'index', + 'context', 'linkType', 'listID', 'showLabel', @@ -86,7 +92,6 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa ]; protected outputNames: (keyof this & string)[] = [ - ...this.outputNames, 'contentChange', ]; From ac5670a51428c58cf02cf139b97c7d358d5b5f30 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Mon, 5 Feb 2024 12:06:11 +0100 Subject: [PATCH 23/30] remove translation (use singular form instead of plural form) --- src/assets/i18n/de.json5 | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c185a13432b..94067fccd0d 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -731,9 +731,6 @@ // "admin.workflow.title": "Administer Workflow", "admin.workflow.title": "Workflows verwalten", - // "admin.workflow.item.workflow": "Workflow", - "admin.workflow.item.workflow": "Workflows", - // "admin.workflow.item.delete": "Delete", "admin.workflow.item.delete": "Löschen", From 9043f63cd2f3ef9d99b4b22db041eddab9f2c95b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 14:57:34 -0600 Subject: [PATCH 24/30] Bug fix to GitHub CI build. Set compose project name via env variable. Also add default name if unspecified --- .github/workflows/build.yml | 2 ++ docker/cli.yml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2680420a21..52f20470a3c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,8 @@ jobs: #CHROME_VERSION: "90.0.4430.212-1" # Bump Node heap size (OOM in CI after upgrading to Angular 15) NODE_OPTIONS: '--max-old-space-size=4096' + # Project name to use when running docker-compose prior to e2e tests + COMPOSE_PROJECT_NAME: 'ci' strategy: # Create a matrix of Node versions to test against (in parallel) matrix: diff --git a/docker/cli.yml b/docker/cli.yml index c2664fdf3e1..cc266b186f9 100644 --- a/docker/cli.yml +++ b/docker/cli.yml @@ -16,8 +16,9 @@ version: "3.7" networks: # Default to using network named 'dspacenet' from docker-compose-rest.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + # If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in) default: - name: ${COMPOSE_PROJECT_NAME}_dspacenet + name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet external: true services: dspace-cli: From be0a6c71efae4c5d2eda09940587338e23bea630 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 16:59:52 -0600 Subject: [PATCH 25/30] Resync docker-compose-ci and docker/docker-compose-rest with DSpace/DSpace docker scripts --- docker/docker-compose-ci.yml | 36 +++++++++++++++------------------- docker/docker-compose-rest.yml | 18 +++++++---------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index edbb5b07598..07993e20c62 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -33,11 +33,11 @@ services: # Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit. # This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly. solr__D__statistics__P__autoCommit: 'false' + image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}" depends_on: - dspacedb - image: dspace/dspace:latest-test networks: - dspacenet: + - dspacenet ports: - published: 8080 target: 8080 @@ -45,8 +45,6 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - # Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below) - - solr_configs:/dspace/solr # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep # 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any) @@ -70,21 +68,18 @@ services: PGDATA: /pgdata image: dspace/dspace-postgres-pgcrypto:loadsql networks: - dspacenet: + - dspacenet stdin_open: true tty: true volumes: + # Keep Postgres data directory between reboots - pgdata:/pgdata # DSpace Solr container dspacesolr: container_name: dspacesolr - # Uses official Solr image at https://hub.docker.com/_/solr/ - image: solr:8.11-slim - # Needs main 'dspace' container to start first to guarantee access to solr_configs - depends_on: - - dspace + image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}" networks: - dspacenet: + - dspacenet ports: - published: 8983 target: 8983 @@ -92,9 +87,6 @@ services: tty: true working_dir: /var/solr/data volumes: - # Mount our "solr_configs" volume available under the Solr's configsets folder (in a 'dspace' subfolder) - # This copies the Solr configs from main 'dspace' container into 'dspacesolr' via that volume - - solr_configs:/opt/solr/server/solr/configsets/dspace # Keep Solr data directory between reboots - solr_data:/var/solr/data # Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr @@ -103,14 +95,18 @@ services: - '-c' - | init-var-solr - precreate-core authority /opt/solr/server/solr/configsets/dspace/authority - precreate-core oai /opt/solr/server/solr/configsets/dspace/oai - precreate-core search /opt/solr/server/solr/configsets/dspace/search - precreate-core statistics /opt/solr/server/solr/configsets/dspace/statistics + precreate-core authority /opt/solr/server/solr/configsets/authority + cp -r /opt/solr/server/solr/configsets/authority/* authority + precreate-core oai /opt/solr/server/solr/configsets/oai + cp -r /opt/solr/server/solr/configsets/oai/* oai + precreate-core search /opt/solr/server/solr/configsets/search + cp -r /opt/solr/server/solr/configsets/search/* search + precreate-core statistics /opt/solr/server/solr/configsets/statistics + cp -r /opt/solr/server/solr/configsets/statistics/* statistics + precreate-core qaevent /opt/solr/server/solr/configsets/qaevent + cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent exec solr -f volumes: assetstore: pgdata: solr_data: - # Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above) - solr_configs: \ No newline at end of file diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index ea766600efa..e1577ec8375 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -43,7 +43,7 @@ services: depends_on: - dspacedb networks: - dspacenet: + - dspacenet ports: - published: 8080 target: 8080 @@ -51,8 +51,6 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - # Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below) - - solr_configs:/dspace/solr # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep # 2. Then, run database migration to init database tables @@ -69,25 +67,23 @@ services: container_name: dspacedb environment: PGDATA: /pgdata - image: dspace/dspace-postgres-pgcrypto + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}" networks: - dspacenet: + - dspacenet ports: - published: 5432 target: 5432 stdin_open: true tty: true volumes: + # Keep Postgres data directory between reboots - pgdata:/pgdata # DSpace Solr container dspacesolr: container_name: dspacesolr image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}" - # Needs main 'dspace' container to start first to guarantee access to solr_configs - depends_on: - - dspace networks: - dspacenet: + - dspacenet ports: - published: 8983 target: 8983 @@ -115,10 +111,10 @@ services: cp -r /opt/solr/server/solr/configsets/search/* search precreate-core statistics /opt/solr/server/solr/configsets/statistics cp -r /opt/solr/server/solr/configsets/statistics/* statistics + precreate-core qaevent /opt/solr/server/solr/configsets/qaevent + cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent exec solr -f volumes: assetstore: pgdata: solr_data: - # Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above) - solr_configs: From 2fb393db7f8d45922f3a529b3bc1c34d2625818c Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 1 Feb 2024 21:02:55 +0100 Subject: [PATCH 26/30] Removed the AbstractBrowseByTypeComponent --- .../abstract-browse-by-type.component.ts | 32 ------------------- .../browse-by-metadata-page.component.ts | 26 +++++++++++---- .../browse-by-page.component.spec.ts | 3 +- .../browse-by-switcher/browse-by-decorator.ts | 10 +++--- .../browse-by-switcher.component.spec.ts | 3 +- .../browse-by-switcher.component.ts | 5 ++- .../browse-by-taxonomy-page.component.ts | 27 +++++++++++++--- 7 files changed, 51 insertions(+), 55 deletions(-) delete mode 100644 src/app/browse-by/abstract-browse-by-type.component.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 deleted file mode 100644 index 5bd39cfc509..00000000000 --- a/src/app/browse-by/abstract-browse-by-type.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, Input, OnDestroy } from '@angular/core'; -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'; - -@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 4c897b40f8f..499ba441f6a 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,5 +1,5 @@ -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; +import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; +import { Component, Inject, OnInit, OnDestroy, Input } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @@ -23,8 +23,8 @@ 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 } 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'; +import { Context } from '../../core/shared/context.model'; export const BBM_PAGINATION_ID = 'bbm'; @@ -40,7 +40,17 @@ export const BBM_PAGINATION_ID = 'bbm'; * 'dc.contributor.*' */ @rendersBrowseBy(BrowseByDataType.Metadata) -export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { +export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link BrowseByDataType} of this Component + */ + @Input() browseByType: BrowseByDataType; /** * The list of browse-entries to display @@ -77,6 +87,11 @@ export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent */ currentSort$: Observable; + /** + * List of subscriptions + */ + subs: Subscription[] = []; + /** * The default browse id to resort to when none is provided */ @@ -129,7 +144,6 @@ export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent @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, @@ -273,7 +287,7 @@ export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent } ngOnDestroy(): void { - super.ngOnDestroy(); + this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe()); this.paginationService.clearPagination(this.paginationConfig.id); } 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 3f9271a67dd..25483028ebd 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 @@ -9,7 +9,6 @@ import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; 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'; @@ -20,7 +19,7 @@ import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; selector: '', template: '', }) -class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +class BrowseByTestComponent { } class TestBrowseByPageBrowseDefinition extends BrowseDefinition { 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 835c34ebcbc..62e666227d4 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,6 +1,6 @@ +import { Component } from '@angular/core'; import { hasNoValue } from '../../shared/empty.util'; 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 { BrowseByDataType } from './browse-by-data-type'; @@ -8,7 +8,7 @@ import { BrowseByDataType } from './browse-by-data-type'; export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any; -const map: Map>>> = new Map(); +const map: Map>>> = new Map(); /** * Decorator used for rendering Browse-By pages by type @@ -41,12 +41,12 @@ export function rendersBrowseBy(browseByType: BrowseByDataType, context = DEFAUL * @param context The context to match * @param theme the theme to match */ -export function getComponentByBrowseByType(browseByType: BrowseByDataType, context: Context, theme: string): GenericConstructor { - let contextMap: Map>> = 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); + let themeMap: Map> = contextMap.get(context); if (hasNoValue(themeMap)) { themeMap = contextMap.get(DEFAULT_BROWSE_BY_CONTEXT); } 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 418dfd45e15..65e8c87c619 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 @@ -8,7 +8,6 @@ import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-d 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'; import { BrowseByDataType } from './browse-by-data-type'; @rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType) @@ -17,7 +16,7 @@ import { BrowseByDataType } from './browse-by-data-type'; selector: '', template: '', }) -class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +class BrowseByTestComponent { } describe('BrowseBySwitcherComponent', () => { 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 99ad720dd3d..0c200c3453e 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 @@ -2,7 +2,6 @@ import { Component, Input } from '@angular/core'; 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'; import { Context } from '../../core/shared/context.model'; @@ -10,7 +9,7 @@ import { Context } from '../../core/shared/context.model'; selector: 'ds-browse-by-switcher', templateUrl: '../../shared/abstract-component-loader/abstract-component-loader.component.html' }) -export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { +export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { @Input() context: Context; @@ -26,7 +25,7 @@ export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent< 'browseByType', ]; - public getComponent(): GenericConstructor { + 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 f005c66c9e0..fb2f28c8c50 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,14 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, Input } 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 } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; 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'; +import { Context } from '../../core/shared/context.model'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -19,7 +19,17 @@ import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; * Component for browsing items by metadata in a hierarchical controlled vocabulary */ @rendersBrowseBy(BrowseByDataType.Hierarchy) -export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { +export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link BrowseByDataType} of this Component + */ + @Input() browseByType: BrowseByDataType; /** * The {@link VocabularyOptions} object @@ -56,10 +66,14 @@ export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent */ browseDefinition$: Observable; + /** + * Subscriptions to track + */ + subs: Subscription[] = []; + public constructor( protected route: ActivatedRoute, ) { - super(); } ngOnInit(): void { @@ -108,4 +122,7 @@ export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent }; } + ngOnDestroy(): void { + this.subs.forEach((sub: Subscription) => sub.unsubscribe()); + } } From 90a277fbeb33ca29060d8ffe59954600d0eb9466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:29:37 +0000 Subject: [PATCH 27/30] Bump follow-redirects from 1.15.3 to 1.15.5 Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.5. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.5) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6e04ab27b2a..786a832e56b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5859,9 +5859,9 @@ flatted@^3.1.0, flatted@^3.2.7: integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== for-each@^0.3.3: version "0.3.3" From 3e33785311444682eaf1f38ffc0bb8e0b3e3f353 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 7 Feb 2024 11:55:44 +1300 Subject: [PATCH 28/30] Improve cfg handling in item-status.component.ts --- .../item-status/item-status.component.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index d3a5e033d71..759045b3dca 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -3,7 +3,7 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; -import { concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, toArray } from 'rxjs/operators'; +import {concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, tap, toArray} from 'rxjs/operators'; import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; @@ -17,6 +17,7 @@ import { ConfigurationProperty } from '../../../core/shared/configuration-proper import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { IdentifierData } from '../../../shared/object-list/identifier-data/identifier-data.model'; import { OrcidAuthService } from '../../../core/orcid/orcid-auth.service'; +import {getDSORoute} from "../../../app-routing-paths"; @Component({ selector: 'ds-item-status', @@ -107,7 +108,19 @@ export class ItemStatusComponent implements OnInit { // Observable for configuration determining whether the Register DOI feature is enabled let registerConfigEnabled$: Observable = this.configurationService.findByPropertyName('identifiers.item-status.register-doi').pipe( getFirstCompletedRemoteData(), - map((enabledRD: RemoteData) => enabledRD.hasSucceeded && enabledRD.payload.values.length > 0) + map((response: RemoteData) => { + // Return true if a successful response with a 'true' value was retrieved, otherwise return false + if (response.hasSucceeded) { + const payload = response.payload; + if (payload.values.length > 0 && hasValue(payload.values[0])) { + return payload.values[0] === 'true'; + } else { + return false; + } + } else { + return false; + } + }) ); /** From 68475d1ed61bd231de1a1efdb2feb3bf638108c2 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 7 Feb 2024 12:09:16 +1300 Subject: [PATCH 29/30] Improve cfg handling in item-status.component.ts --- .../edit-item-page/item-status/item-status.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 759045b3dca..8b4dc88d9d1 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -3,7 +3,7 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; -import {concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, tap, toArray} from 'rxjs/operators'; +import {concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, toArray} from 'rxjs/operators'; import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; @@ -17,7 +17,6 @@ import { ConfigurationProperty } from '../../../core/shared/configuration-proper import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { IdentifierData } from '../../../shared/object-list/identifier-data/identifier-data.model'; import { OrcidAuthService } from '../../../core/orcid/orcid-auth.service'; -import {getDSORoute} from "../../../app-routing-paths"; @Component({ selector: 'ds-item-status', From f7dbdb37eac5182f99c2607b9f8906c9176ac1f7 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 8 Feb 2024 18:12:12 +0100 Subject: [PATCH 30/30] 102415: Added additional i18n keys for the relationship modal --- src/assets/i18n/en.json5 | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f742273edbe..6dab95b5ce5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2300,6 +2300,8 @@ "journalissue.page.titleprefix": "Journal Issue: ", + "journalissue.search.results.head": "Journal Issue Search Results", + "journalvolume.listelement.badge": "Journal Volume", @@ -2314,6 +2316,8 @@ "journalvolume.page.volume": "Volume", + "journalvolume.search.results.head": "Journal Volume Search Results", + "iiifsearchable.listelement.badge": "Document Media", @@ -3043,10 +3047,14 @@ "relationships.isIssueOf": "Journal Issues", + "relationships.isIssueOf.JournalIssue": "Journal Issue", + "relationships.isJournalIssueOf": "Journal Issue", "relationships.isJournalOf": "Journals", + "relationships.isJournalVolumeOf": "Journal Volume", + "relationships.isOrgUnitOf": "Organizational Units", "relationships.isPersonOf": "Authors", @@ -3063,6 +3071,8 @@ "relationships.isVolumeOf": "Journal Volumes", + "relationships.isVolumeOf.JournalVolume": "Journal Volume", + "relationships.isContributorOf": "Contributors", "relationships.isContributorOf.OrgUnit": "Contributor (Organizational Unit)", @@ -3698,6 +3708,16 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.arxiv": "arXiv ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.orcidWorks": "ORCID ({{ count }})", + + "submission.sections.describe.relationship-lookup.search-tab.tab-title.crossref": "CrossRef ({{ count }})", + + "submission.sections.describe.relationship-lookup.search-tab.tab-title.scopus": "Scopus ({{ count }})", + + "submission.sections.describe.relationship-lookup.search-tab.tab-title.openaireFunding": "Funding OpenAIRE ({{ count }})", + + "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournalIssn": "Sherpa Journals by ISSN ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfPublication": "Search for Funding Agencies", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Search for Funding", @@ -3725,6 +3745,8 @@ "submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})", + "submission.sections.describe.relationship-lookup.title.Journal": "Journal", + "submission.sections.describe.relationship-lookup.title.isJournalIssueOfPublication": "Journal Issues", "submission.sections.describe.relationship-lookup.title.JournalIssue": "Journal Issues",