From 8e4247c2ac8152b1a8c74188d481e5df40b8bf5d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 21 Apr 2023 00:06:02 +0200 Subject: [PATCH 1/9] 101577: Themed ThumbnailComponent --- .../edit-bitstream-page.component.html | 2 +- ...-search-result-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- .../journal-issue.component.html | 2 +- .../journal-volume.component.html | 2 +- .../item-pages/journal/journal.component.html | 2 +- ...-search-result-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- .../org-unit/org-unit.component.html | 4 +- .../item-pages/person/person.component.html | 4 +- .../item-pages/project/project.component.html | 4 +- ...ult-list-submission-element.component.html | 2 +- .../full-file-section.component.html | 4 +- .../publication/publication.component.html | 2 +- .../untyped-item/untyped-item.component.html | 2 +- .../item-detail-preview.component.html | 2 +- .../collection-grid-element.component.html | 8 ++-- .../community-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- ...-search-result-grid-element.component.html | 8 ++-- src/app/shared/shared.module.ts | 2 + .../file/section-upload-file.component.html | 4 +- .../thumbnail/themed-thumbnail.component.ts | 44 +++++++++++++++++++ 26 files changed, 108 insertions(+), 62 deletions(-) create mode 100644 src/app/thumbnail/themed-thumbnail.component.ts diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html index 4d3b948a58..769cc57ce2 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html index 028876b3d0..6113605802 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html index 65ff75a731..242230e375 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html index 0c5824c6d6..dd6370f7da 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html index 5847be7dd2..46f9452478 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html @@ -9,7 +9,7 @@
- +
- +
- +
- - + +
- - + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html index 680a9909bc..c812685f3c 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html index 204f8fc8cb..eda01bc711 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html @@ -8,14 +8,14 @@ rel="noopener noreferrer" [routerLink]="[itemPageRoute]" class="card-img-top full-width">
- - + +
- - + +
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index c9ea8fb549..feac07627b 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -9,12 +9,12 @@
- - +
- - +
- - + diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html index 13787bb925..1db9b85769 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html @@ -1,6 +1,6 @@
- +
- +
@@ -55,7 +55,7 @@
- +
diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index bace9fcd0a..eee5c16a28 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -19,7 +19,7 @@
- + diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index 04794717f1..232ee29483 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -22,7 +22,7 @@
- + diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html index 61e2955deb..8a32f10788 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html @@ -10,7 +10,7 @@
- + diff --git a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html index d47e897edc..9904fe7c55 100644 --- a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html +++ b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +

{{object.name}}

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

{{object.name}}

diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html index 739fa6c7a8..126442e8f5 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +
diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html index d8c253c8a9..96f2312089 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html @@ -1,11 +1,11 @@
- - + + - - + +
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html index d2454b28e6..a7c805f2da 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html @@ -6,14 +6,14 @@
- - + +
- - + +
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 715ee66a99..2c3426da13 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -43,6 +43,7 @@ import { ErrorComponent } from './error/error.component'; import { LoadingComponent } from './loading/loading.component'; import { PaginationComponent } from './pagination/pagination.component'; import { ThumbnailComponent } from '../thumbnail/thumbnail.component'; +import { ThemedThumbnailComponent } from '../thumbnail/themed-thumbnail.component'; import { SearchFormComponent } from './search-form/search-form.component'; import { SearchResultGridElementComponent } from './object-grid/search-result-grid-element/search-result-grid-element.component'; import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component'; @@ -249,6 +250,7 @@ const COMPONENTS = [ SidebarFilterComponent, SidebarFilterSelectedOptionComponent, ThumbnailComponent, + ThemedThumbnailComponent, UploaderComponent, FileDropzoneNoUploaderComponent, ItemListPreviewComponent, diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 1bfc52529b..5f8fe3b9ee 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -1,8 +1,8 @@
- - + +
diff --git a/src/app/thumbnail/themed-thumbnail.component.ts b/src/app/thumbnail/themed-thumbnail.component.ts new file mode 100644 index 0000000000..2a8d809104 --- /dev/null +++ b/src/app/thumbnail/themed-thumbnail.component.ts @@ -0,0 +1,44 @@ +import { ThemedComponent } from '../shared/theme-support/themed.component'; +import { Component, Input } from '@angular/core'; +import { ThumbnailComponent } from './thumbnail.component'; +import { Bitstream } from '../core/shared/bitstream.model'; +import { RemoteData } from '../core/data/remote-data'; + +@Component({ + selector: 'ds-themed-thumbnail', + styleUrls: [], + templateUrl: '../shared/theme-support/themed.component.html', +}) +export class ThemedThumbnailComponent extends ThemedComponent { + + @Input() thumbnail: Bitstream | RemoteData; + + @Input() defaultImage?: string | null; + + @Input() alt?: string; + + @Input() placeholder?: string; + + @Input() limitWidth?: boolean; + + protected inAndOutputNames: (keyof ThumbnailComponent & keyof this)[] = [ + 'thumbnail', + 'defaultImage', + 'alt', + 'placeholder', + 'limitWidth', + ]; + + protected getComponentName(): string { + return 'ThumbnailComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../themes/${themeName}/app/thumbnail/thumbnail.component`); + } + + protected importUnthemedComponent(): Promise { + return import('./thumbnail.component'); + } + +} From 30cbcbb7c8e6858ddcd169b34345d6e63e49c567 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 21 Apr 2023 00:17:23 +0200 Subject: [PATCH 2/9] 101577: Added themeable ThumbnailComponent to custom theme --- .../custom/app/thumbnail/thumbnail.component.html | 0 .../custom/app/thumbnail/thumbnail.component.scss | 0 .../custom/app/thumbnail/thumbnail.component.ts | 12 ++++++++++++ src/themes/custom/theme.module.ts | 4 +++- 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/themes/custom/app/thumbnail/thumbnail.component.html create mode 100644 src/themes/custom/app/thumbnail/thumbnail.component.scss create mode 100644 src/themes/custom/app/thumbnail/thumbnail.component.ts diff --git a/src/themes/custom/app/thumbnail/thumbnail.component.html b/src/themes/custom/app/thumbnail/thumbnail.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/thumbnail/thumbnail.component.scss b/src/themes/custom/app/thumbnail/thumbnail.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/thumbnail/thumbnail.component.ts b/src/themes/custom/app/thumbnail/thumbnail.component.ts new file mode 100644 index 0000000000..406183c8e7 --- /dev/null +++ b/src/themes/custom/app/thumbnail/thumbnail.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { ThumbnailComponent as BaseComponent } from '../../../../app/thumbnail/thumbnail.component'; + +@Component({ + selector: 'ds-thumbnail', + // styleUrls: ['./thumbnail.component.scss'], + styleUrls: ['../../../../app/thumbnail/thumbnail.component.scss'], + // templateUrl: './thumbnail.component.html', + templateUrl: '../../../../app/thumbnail/thumbnail.component.html', +}) +export class ThumbnailComponent extends BaseComponent { +} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index e2e97b9087..d028f3b7e4 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -84,6 +84,7 @@ import { SearchModule } from '../../app/shared/search/search.module'; import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; +import { ThumbnailComponent } from './app/thumbnail/thumbnail.component'; const DECLARATIONS = [ FileSectionComponent, @@ -126,7 +127,8 @@ const DECLARATIONS = [ NavbarComponent, HeaderNavbarWrapperComponent, BreadcrumbsComponent, - FeedbackComponent + FeedbackComponent, + ThumbnailComponent, ]; @NgModule({ From 0559e1ebc1cda81336f59b7fc94e0d7b206f5e4b Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 17 May 2023 15:22:37 +0200 Subject: [PATCH 3/9] 101577: Added changes parameter to the ThumbnailComponent's ngOnChanges --- src/app/thumbnail/thumbnail.component.spec.ts | 14 +++++++------- src/app/thumbnail/thumbnail.component.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index eea585f9f8..050df74713 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -49,7 +49,7 @@ describe('ThumbnailComponent', () => { comp.src = 'http://bit.stream'; comp.defaultImage = 'http://default.img'; comp.errorHandler(); - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); @@ -61,7 +61,7 @@ describe('ThumbnailComponent', () => { comp.errorHandler(); expect(comp.src).toBe(null); - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const placeholder = fixture.debugElement.query(By.css('div.thumbnail-placeholder')).nativeElement; expect(placeholder.innerHTML).toBe('TRANSLATED ' + comp.placeholder); @@ -84,7 +84,7 @@ describe('ThumbnailComponent', () => { it('should display an image', () => { comp.thumbnail = thumbnail; - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('src')).toBe(comp.thumbnail._links.content.href); @@ -92,7 +92,7 @@ describe('ThumbnailComponent', () => { it('should include the alt text', () => { comp.thumbnail = thumbnail; - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); @@ -113,7 +113,7 @@ describe('ThumbnailComponent', () => { it('should show a loading animation', () => { comp.thumbnail = thumbnail; - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); expect(de.query(By.css('ds-loading'))).toBeTruthy(); }); @@ -134,7 +134,7 @@ describe('ThumbnailComponent', () => { it('should display an image', () => { comp.thumbnail = thumbnail; - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('src')).toBe(comp.thumbnail.payload._links.content.href); @@ -142,7 +142,7 @@ describe('ThumbnailComponent', () => { it('should display the alt text', () => { comp.thumbnail = thumbnail; - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 3e122cde78..90c48d19bc 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges } from '@angular/core'; +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Bitstream } from '../core/shared/bitstream.model'; import { hasValue } from '../shared/empty.util'; import { RemoteData } from '../core/data/remote-data'; @@ -51,7 +51,7 @@ export class ThumbnailComponent implements OnChanges { * Resolve the thumbnail. * Use a default image if no actual image is available. */ - ngOnChanges(): void { + ngOnChanges(changes: SimpleChanges): void { if (this.thumbnail === undefined || this.thumbnail === null) { return; } From 10d5f3d0afbf1205d102def28338cd5f48f0132b Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 23 Apr 2023 22:15:06 +0200 Subject: [PATCH 4/9] 101577: Ensure renderComponentInstance is called in ngOnChanges if the component is not initialised yet --- ...arch-result-grid-element.component.spec.ts | 5 +- ...arch-result-grid-element.component.spec.ts | 5 +- .../shared/theme-support/themed.component.ts | 78 ++++++++++++------- 3 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts index 2cb0413bbc..9a28fd745f 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.spec.ts @@ -14,6 +14,8 @@ import { By } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths'; import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; describe('CollectionAdminSearchResultGridElementComponent', () => { let component: CollectionAdminSearchResultGridElementComponent; @@ -45,7 +47,8 @@ describe('CollectionAdminSearchResultGridElementComponent', () => { providers: [ { provide: TruncatableService, useValue: mockTruncatableService }, { provide: BitstreamDataService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: ThemeService, useValue: getMockThemeService() }, ] }) .compileComponents(); diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts index 17ce2cd7a1..1052b9d38e 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.spec.ts @@ -16,6 +16,8 @@ import { CommunitySearchResult } from '../../../../../shared/object-collection/s import { Community } from '../../../../../core/shared/community.model'; import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths'; import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; describe('CommunityAdminSearchResultGridElementComponent', () => { let component: CommunityAdminSearchResultGridElementComponent; @@ -47,7 +49,8 @@ describe('CommunityAdminSearchResultGridElementComponent', () => { providers: [ { provide: TruncatableService, useValue: mockTruncatableService }, { provide: BitstreamDataService, useValue: {} }, - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: ThemeService, useValue: getMockThemeService() }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 2ff0713f46..1df7f77f08 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -10,8 +10,8 @@ import { ChangeDetectorRef, OnChanges } from '@angular/core'; -import { hasValue, isNotEmpty } from '../empty.util'; -import { from as fromPromise, Observable, of as observableOf, Subscription } from 'rxjs'; +import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { combineLatest, from as fromPromise, Observable, of as observableOf, Subscription } from 'rxjs'; import { ThemeService } from './theme.service'; import { catchError, switchMap, map } from 'rxjs/operators'; import { GenericConstructor } from '../../core/shared/generic-constructor'; @@ -25,6 +25,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges @ViewChild('vcr', { read: ViewContainerRef }) vcr: ViewContainerRef; protected compRef: ComponentRef; + protected lazyLoadObs: Observable; protected lazyLoadSub: Subscription; protected themeSub: Subscription; @@ -43,17 +44,24 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges protected abstract importUnthemedComponent(): Promise; ngOnChanges(changes: SimpleChanges): void { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); + if (hasNoValue(this.compRef)) { + // sometimes the component has not been initialized yet, so it first needs to be initialized + // before being called again + this.initComponentInstance(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); + } + } } } ngOnInit(): void { this.destroyComponentInstance(); - this.themeSub = this.themeService.getThemeName$().subscribe(() => { - this.renderComponentInstance(); - }); + this.initComponentInstance(); } ngOnDestroy(): void { @@ -61,32 +69,48 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges this.destroyComponentInstance(); } - protected renderComponentInstance(): void { - this.destroyComponentInstance(); + initComponentInstance(changes?: SimpleChanges) { + this.themeSub = this.themeService?.getThemeName$().subscribe(() => { + this.renderComponentInstance(changes); + }); + } + protected renderComponentInstance(changes?: SimpleChanges): void { if (hasValue(this.lazyLoadSub)) { this.lazyLoadSub.unsubscribe(); } - this.lazyLoadSub = this.resolveThemedComponent(this.themeService.getThemeName()).pipe( - switchMap((themedFile: any) => { - if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) { - // if the file is not null, and exports a component with the specified name, - // return that component - return [themedFile[this.getComponentName()]]; - } else { - // otherwise import and return the default component - return fromPromise(this.importUnthemedComponent()).pipe( - map((unthemedFile: any) => { - return unthemedFile[this.getComponentName()]; - }) - ); - } - }), - ).subscribe((constructor: GenericConstructor) => { + if (hasNoValue(this.lazyLoadObs)) { + this.destroyComponentInstance(); + + this.lazyLoadObs = combineLatest([ + observableOf(changes), + this.resolveThemedComponent(this.themeService.getThemeName()).pipe( + switchMap((themedFile: any) => { + if (hasValue(themedFile) && hasValue(themedFile[this.getComponentName()])) { + // if the file is not null, and exports a component with the specified name, + // return that component + return [themedFile[this.getComponentName()]]; + } else { + // otherwise import and return the default component + return fromPromise(this.importUnthemedComponent()).pipe( + map((unthemedFile: any) => { + return unthemedFile[this.getComponentName()]; + }) + ); + } + })), + ]); + } + + this.lazyLoadSub = this.lazyLoadObs.subscribe(([simpleChanges, constructor]: [SimpleChanges, GenericConstructor]) => { const factory = this.resolver.resolveComponentFactory(constructor); this.compRef = this.vcr.createComponent(factory); - this.connectInputsAndOutputs(); + if (hasValue(simpleChanges)) { + this.ngOnChanges(simpleChanges); + } else { + this.connectInputsAndOutputs(); + } this.cdr.markForCheck(); }); } From a4a0482d8852cd1bddc60661c27f3f7925e4a3d5 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 17 May 2023 18:10:32 +0200 Subject: [PATCH 5/9] 101577: Fixed ListableObjectComponentLoaderComponent not updating its @listableObjectComponent components ngOnChanges --- ...-object-component-loader.component.spec.ts | 2 +- ...table-object-component-loader.component.ts | 35 ++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) 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 edf0b3ea7c..a932eb17a4 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 @@ -148,7 +148,7 @@ describe('ListableObjectComponentLoaderComponent', () => { (listableComponent as any).reloadedObject.emit(reloadedObject); tick(); - expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject); + expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject, undefined); })); it('should re-emit it as a contentChange', fakeAsync(() => { 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 8adbcbeec3..6d6048afa3 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 @@ -19,8 +19,8 @@ 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 { Subscription } from 'rxjs'; +import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; +import { Subscription, combineLatest, of as observableOf, Observable } from 'rxjs'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { take } from 'rxjs/operators'; import { ThemeService } from '../../../theme-support/theme.service'; @@ -147,8 +147,18 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges * Whenever the inputs change, update the inputs of the dynamic component */ ngOnChanges(changes: SimpleChanges): void { - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); + 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); + } + } } } @@ -158,7 +168,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges .forEach((subscription) => subscription.unsubscribe()); } - private instantiateComponent(object) { + private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void { this.initBadges(); @@ -177,14 +187,21 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges [this.badges.nativeElement], ]); - this.connectInputsAndOutputs(); + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); + } if ((this.compRef.instance as any).reloadedObject) { - (this.compRef.instance as any).reloadedObject.pipe(take(1)).subscribe((reloadedObject: DSpaceObject) => { + combineLatest([ + observableOf(changes), + (this.compRef.instance as any).reloadedObject.pipe(take(1)) as Observable, + ]).subscribe(([simpleChanges, reloadedObject]: [SimpleChanges, DSpaceObject]) => { if (reloadedObject) { this.compRef.destroy(); this.object = reloadedObject; - this.instantiateComponent(reloadedObject); + this.instantiateComponent(reloadedObject, simpleChanges); this.contentChange.emit(reloadedObject); } }); @@ -220,7 +237,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ protected connectInputsAndOutputs(): void { if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.forEach((name: any) => { + this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { this.compRef.instance[name] = this[name]; }); } From 2ca91fd331d29f99ee64358ce93b0d684149715d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 17 May 2023 18:49:06 +0200 Subject: [PATCH 6/9] 101577: Fixed MetadataRepresentationLoaderComponent not updating its @metadataRepresentationComponent components ngOnChanges --- ...-item-metadata-list-element.component.html | 8 +-- ...em-metadata-list-element.component.spec.ts | 2 +- ...-item-metadata-list-element.component.html | 10 +-- ...em-metadata-list-element.component.spec.ts | 2 +- ...etadata-representation-loader.component.ts | 71 +++++++++++++++---- .../item-metadata-list-element.component.html | 2 +- ...em-metadata-list-element.component.spec.ts | 2 +- ...a-representation-list-element.component.ts | 6 +- ...a-representation-list-element.component.ts | 10 ++- ...-text-metadata-list-element.component.html | 2 +- ...xt-metadata-list-element.component.spec.ts | 2 +- 11 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html index 1771f3d2bc..ec4dbd4323 100644 --- a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html +++ b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html @@ -1,12 +1,12 @@ - + - + + [innerHTML]="mdRepresentation.getValue()" + [ngbTooltip]="mdRepresentation.allMetadata(['dc.description']).length > 0 ? descTemplate : null"> diff --git a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts index eff6fd0b31..429f2986b9 100644 --- a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts @@ -34,7 +34,7 @@ describe('OrgUnitItemMetadataListElementComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(OrgUnitItemMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockItemMetadataRepresentation; + comp.mdRepresentation = mockItemMetadataRepresentation; fixture.detectChanges(); }); diff --git a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html index 97632117f4..6f56056781 100644 --- a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html +++ b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html @@ -1,15 +1,15 @@ - - + - + + [innerHTML]="mdRepresentation.getValue()" + [ngbTooltip]="mdRepresentation.allMetadata(['person.jobTitle']).length > 0 ? descTemplate : null"> diff --git a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts index ab801826d6..865c262035 100644 --- a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts @@ -36,7 +36,7 @@ describe('PersonItemMetadataListElementComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PersonItemMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockItemMetadataRepresentation; + comp.mdRepresentation = mockItemMetadataRepresentation; fixture.detectChanges(); }); 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 7077949809..42ee093278 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, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild, OnChanges, SimpleChanges, ComponentRef, ViewContainerRef, ComponentFactory } from '@angular/core'; import { MetadataRepresentation, MetadataRepresentationType @@ -8,19 +8,17 @@ 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 } from '../empty.util'; +import { hasValue, isNotEmpty, hasNoValue } from '../empty.util'; import { ThemeService } from '../theme-support/theme.service'; @Component({ selector: 'ds-metadata-representation-loader', - // styleUrls: ['./metadata-representation-loader.component.scss'], templateUrl: './metadata-representation-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 { - private componentRefInstance: MetadataRepresentationListElementComponent; +export class MetadataRepresentationLoaderComponent implements OnInit, OnChanges { /** * The item or metadata to determine the component for @@ -31,8 +29,8 @@ export class MetadataRepresentationLoaderComponent implements OnInit { } @Input() set mdRepresentation(nextValue: MetadataRepresentation) { this._mdRepresentation = nextValue; - if (hasValue(this.componentRefInstance)) { - this.componentRefInstance.metadataRepresentation = nextValue; + if (hasValue(this.compRef?.instance)) { + this.compRef.instance.mdRepresentation = nextValue; } } @@ -46,6 +44,16 @@ export class MetadataRepresentationLoaderComponent implements OnInit { */ @ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective; + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + + protected inAndOutputNames: (keyof this)[] = [ + 'context', + 'mdRepresentation', + ]; + constructor( private componentFactoryResolver: ComponentFactoryResolver, private themeService: ThemeService, @@ -57,14 +65,41 @@ export class MetadataRepresentationLoaderComponent implements OnInit { * Set up the dynamic child component */ ngOnInit(): void { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + this.instantiateComponent(); + } - const viewContainerRef = this.mdRepDirective.viewContainerRef; + /** + * 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(); - const componentRef = viewContainerRef.createComponent(componentFactory); - this.componentRefInstance = componentRef.instance as MetadataRepresentationListElementComponent; - this.componentRefInstance.metadataRepresentation = this.mdRepresentation; + this.compRef = viewContainerRef.createComponent(componentFactory); + + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); + } } /** @@ -74,4 +109,16 @@ export class MetadataRepresentationLoaderComponent implements OnInit { private 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/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.html b/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.html index 91219c7189..904ea95c20 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.html +++ b/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.spec.ts b/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.spec.ts index 6e48ba3a6f..99052b6b14 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.spec.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-list-element.component.spec.ts @@ -23,7 +23,7 @@ describe('ItemMetadataListElementComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(ItemMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockItemMetadataRepresentation; + comp.mdRepresentation = mockItemMetadataRepresentation; fixture.detectChanges(); })); diff --git a/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component.ts b/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component.ts index 967b09986d..c4a6903129 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component.ts @@ -1,5 +1,5 @@ import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input } from '@angular/core'; import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model'; import { getItemPageRoute } from '../../../../item-page/item-page-routing-paths'; @@ -11,7 +11,7 @@ import { getItemPageRoute } from '../../../../item-page/item-page-routing-paths' * An abstract class for displaying a single ItemMetadataRepresentation */ export class ItemMetadataRepresentationListElementComponent extends MetadataRepresentationListElementComponent implements OnInit { - metadataRepresentation: ItemMetadataRepresentation; + @Input() mdRepresentation: ItemMetadataRepresentation; /** * Route to the item's page @@ -19,6 +19,6 @@ export class ItemMetadataRepresentationListElementComponent extends MetadataRepr itemPageRoute: string; ngOnInit(): void { - this.itemPageRoute = getItemPageRoute(this.metadataRepresentation); + this.itemPageRoute = getItemPageRoute(this.mdRepresentation); } } diff --git a/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.ts b/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.ts index 2e14485fbb..b13dd60601 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model'; +import { Context } from '../../../core/shared/context.model'; @Component({ selector: 'ds-metadata-representation-list-element', @@ -9,8 +10,13 @@ import { MetadataRepresentation } from '../../../core/shared/metadata-representa * An abstract class for displaying a single MetadataRepresentation */ export class MetadataRepresentationListElementComponent { + /** + * The optional context + */ + @Input() context: Context; + /** * The metadata representation of this component */ - metadataRepresentation: MetadataRepresentation; + @Input() mdRepresentation: MetadataRepresentation; } diff --git a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html index 31b670b1a3..cd199836b6 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html +++ b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html @@ -1,3 +1,3 @@
- {{metadataRepresentation.getValue()}} + {{mdRepresentation.getValue()}}
diff --git a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.spec.ts b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.spec.ts index af09d3c204..39ee54c32b 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.spec.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.spec.ts @@ -25,7 +25,7 @@ describe('PlainTextMetadataListElementComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(PlainTextMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockMetadataRepresentation; + comp.mdRepresentation = mockMetadataRepresentation; fixture.detectChanges(); })); From 82a8da60282d0f195ab3d5ac5a3f3b372a85d319 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 17 May 2023 17:57:00 +0100 Subject: [PATCH 7/9] 101577: Fixed ClaimedTaskActionsLoaderComponent not updating its @rendersWorkflowTaskOption components ngOnChanges --- .../claimed-task-actions-loader.component.ts | 73 ++++++++++++++----- 1 file changed, 54 insertions(+), 19 deletions(-) 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 68c597a41c..288fa2e00a 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 @@ -3,18 +3,19 @@ import { ComponentFactoryResolver, EventEmitter, Input, - OnDestroy, OnInit, Output, - ViewChild + ViewChild, + OnChanges, + SimpleChanges, + ComponentRef, } 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 { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; -import { hasValue } from '../../../empty.util'; -import { Subscription } from 'rxjs'; +import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; +import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; @Component({ selector: 'ds-claimed-task-actions-loader', @@ -24,7 +25,7 @@ import { MyDSpaceActionsResult } from '../../mydspace-actions'; * 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, OnDestroy { +export class ClaimedTaskActionsLoaderComponent implements OnInit, OnChanges { /** * The ClaimedTask object */ @@ -47,10 +48,18 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { @ViewChild(ClaimedTaskActionsDirective, {static: true}) claimedTaskActionsDirective: ClaimedTaskActionsDirective; /** - * Array to track all subscriptions and unsubscribe them onDestroy - * @type {Array} + * The reference to the dynamic component */ - protected subs: Subscription[] = []; + protected compRef: ComponentRef; + + /** + * The list of input and output names for the dynamic component + */ + protected inAndOutputNames: (keyof ClaimedTaskActionsAbstractComponent & keyof this)[] = [ + 'object', + 'option', + 'processCompleted', + ]; constructor(private componentFactoryResolver: ComponentFactoryResolver) { } @@ -59,7 +68,29 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { * 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); @@ -67,11 +98,12 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent(componentFactory); - const componentInstance = (componentRef.instance as ClaimedTaskActionsAbstractComponent); - componentInstance.object = this.object; - if (hasValue(componentInstance.processCompleted)) { - this.subs.push(componentInstance.processCompleted.subscribe((result) => this.processCompleted.emit(result))); + this.compRef = viewContainerRef.createComponent(componentFactory); + + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); } } } @@ -81,11 +113,14 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { } /** - * Unsubscribe from open subscriptions + * Connect the in and outputs of this component to the dynamic component, + * to ensure they're in sync */ - ngOnDestroy(): void { - this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); + 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]; + }); + } } } From 98a50763663863a57688f72a11eaac47eb1b3bf8 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 19 May 2023 10:33:25 +0200 Subject: [PATCH 8/9] 101577: Fixed some SearchResultListElementComponents not rendering because of potential undefined dso --- .../claimed-task-search-result-detail-element.component.ts | 6 ++++-- .../pool-search-result-detail-element.component.ts | 6 ++++-- .../claimed-search-result-list-element.component.ts | 6 ++++-- .../pool-search-result-list-element.component.ts | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts index 2ee661ef38..c5d511b867 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts @@ -17,7 +17,7 @@ import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { Item } from '../../../../core/shared/item.model'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { isNotEmpty } from '../../../empty.util'; +import { isNotEmpty, hasValue } from '../../../empty.util'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; /** @@ -88,7 +88,9 @@ export class ClaimedTaskSearchResultDetailElementComponent extends SearchResultD ngOnDestroy() { // This ensures the object is removed from cache, when action is performed on task - this.objectCache.remove(this.dso._links.workflowitem.href); + if (hasValue(this.dso)) { + this.objectCache.remove(this.dso._links.workflowitem.href); + } } } diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts index 6dec14f9cb..a5eca3c148 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts @@ -17,7 +17,7 @@ import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { Item } from '../../../../core/shared/item.model'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { isNotEmpty } from '../../../empty.util'; +import { isNotEmpty, hasValue } from '../../../empty.util'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; /** @@ -89,7 +89,9 @@ export class PoolSearchResultDetailElementComponent extends SearchResultDetailEl ngOnDestroy() { // This ensures the object is removed from cache, when action is performed on task - this.objectCache.remove(this.dso._links.workflowitem.href); + if (hasValue(this.dso)) { + this.objectCache.remove(this.dso._links.workflowitem.href); + } } } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index 237a5f516e..3da443551f 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -22,7 +22,7 @@ import { ObjectCacheService } from '../../../../core/cache/object-cache.service' import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; import { Item } from '../../../../core/shared/item.model'; import { mergeMap, tap } from 'rxjs/operators'; -import { isNotEmpty } from '../../../empty.util'; +import { isNotEmpty, hasValue } from '../../../empty.util'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -101,7 +101,9 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle ngOnDestroy() { // This ensures the object is removed from cache, when action is performed on task - this.objectCache.remove(this.dso._links.workflowitem.href); + if (hasValue(this.dso)) { + this.objectCache.remove(this.dso._links.workflowitem.href); + } } } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index cb924af40f..674b62733f 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -23,7 +23,7 @@ import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interfac import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; import { Item } from '../../../../core/shared/item.model'; -import { isNotEmpty } from '../../../empty.util'; +import { isNotEmpty, hasValue } from '../../../empty.util'; /** * This component renders pool task object for the search result in the list view. @@ -111,6 +111,8 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen ngOnDestroy() { // This ensures the object is removed from cache, when action is performed on task - this.objectCache.remove(this.dso._links.workflowitem.href); + if (hasValue(this.dso)) { + this.objectCache.remove(this.dso._links.workflowitem.href); + } } } From 92f58f0e8a79c60d0fb0bd98a7223986776c8092 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 19 May 2023 14:01:35 +0200 Subject: [PATCH 9/9] 101577: Fixed default thumbnail not working when thumbnail isn't defined --- src/app/thumbnail/thumbnail.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 90c48d19bc..7dfa2c1b5e 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -53,6 +53,7 @@ export class ThumbnailComponent implements OnChanges { */ ngOnChanges(changes: SimpleChanges): void { if (this.thumbnail === undefined || this.thumbnail === null) { + this.src = this.defaultImage; return; } if (this.thumbnail instanceof Bitstream) {