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 6454198340..26223e503d 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 f379efbaf1..c6a2f5bcc3 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" [attr.title]="'search.results.view-result' | translate">
- - + +
- - + +
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 0477ef2324..b7e1330c86 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" [attr.title]="'search.results.view-result' | translate">
- - + +
- - + +
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 d994ae8411..e4c9ada3ca 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" [attr.title]="'search.results.view-result' | translate">
- - + +
- - + +
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 6dfc806c72..75f4587a3d 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 @@ -7,7 +7,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 a11cd384e0..fe26fd7063 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" [attr.title]="'search.results.view-result' | translate">
- - + +
- - + +
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 60c9db31f9..cf47f947cc 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" [attr.title]="'search.results.view-result' | translate">
- - + +
- - + +
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 0aec93a884..c79d19e267 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 @@ -7,12 +7,12 @@
- - +
- - +
- - + 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 895cf52223..b9ebf19b67 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/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 6123dc3dc8..e264958738 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 dcf243c63b..3749f63964 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 @@ -17,7 +17,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 ff98057334..904b7e039c 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 @@ -18,7 +18,7 @@
- +
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/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 6a14aeb5bc..c0dc1cad02 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,20 +3,21 @@ 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 { Item } from '../../../../core/shared/item.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; @Component({ selector: 'ds-claimed-task-actions-loader', @@ -26,7 +27,7 @@ import { WorkflowItem } from '../../../../core/submission/models/workflowitem.mo * 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 item object that belonging to the ClaimedTask object */ @@ -59,10 +60,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) { } @@ -71,7 +80,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); @@ -79,13 +110,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.item = this.item; - componentInstance.object = this.object; - componentInstance.workflowitem = this.workflowitem; - 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(); } } } @@ -95,11 +125,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]; + }); + } } } 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 e9bc294e9d..e893fe807b 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 as any).reloadedObject.emit(reloadedObject); tick(200); - 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 35950d85b9..6d2307a7f0 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 @@ -12,7 +12,7 @@ import { ViewChild } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Subscription, combineLatest, of as observableOf, Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { ListableObject } from '../listable-object.model'; @@ -22,7 +22,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'; @@ -126,8 +126,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); + } + } } } @@ -137,7 +147,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges .forEach((subscription) => subscription.unsubscribe()); } - private instantiateComponent(object) { + private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void { const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context); @@ -151,16 +161,21 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges } ); - 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.cdr.detectChanges(); this.contentChange.emit(reloadedObject); } @@ -184,7 +199,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]; }); } 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 7789f9957e..287ec77050 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 @@ -14,7 +14,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'; import { Context } from 'src/app/core/shared/context.model'; @@ -86,7 +86,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/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 229e36deef..23efc7aea4 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 @@ -6,7 +6,7 @@
- + 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 c07977d77d..ded6f88cfb 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 @@ -14,7 +14,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'; import { Context } from 'src/app/core/shared/context.model'; @@ -87,7 +87,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-grid/collection-grid-element/collection-grid-element.component.html b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html index 4d6d5cb1c3..51f006458d 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 c9833b8829..4102f17506 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 694c6f565f..d2b90e2314 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 c8f3ffca0a..b676409f5b 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 48fb01200b..3d6e251238 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 @@ -5,14 +5,14 @@
- - + +
- - + +
diff --git a/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.html b/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.html index 8d3afea273..e2f55ce10c 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.html +++ b/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.html @@ -1,9 +1,9 @@ diff --git a/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.spec.ts b/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.spec.ts index 32919d9758..3527b9fddd 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.spec.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.spec.ts @@ -30,7 +30,7 @@ describe('BrowseLinkMetadataListElementComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(BrowseLinkMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockMetadataRepresentation; + comp.mdRepresentation = mockMetadataRepresentation; fixture.detectChanges(); })); @@ -46,7 +46,7 @@ describe('BrowseLinkMetadataListElementComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(BrowseLinkMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockMetadataRepresentationWithUrl; + comp.mdRepresentation = mockMetadataRepresentationWithUrl; fixture.detectChanges(); })); diff --git a/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.ts b/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.ts index 0eb0ce05b0..51907b5641 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component.ts @@ -20,9 +20,9 @@ export class BrowseLinkMetadataListElementComponent extends MetadataRepresentati * expects 'startsWith' (eg browse by date) or 'value' (eg browse by title) */ getQueryParams() { - let queryParams = {startsWith: this.metadataRepresentation.getValue()}; - if (this.metadataRepresentation.browseDefinition.metadataBrowse) { - return {value: this.metadataRepresentation.getValue()}; + let queryParams = {startsWith: this.mdRepresentation.getValue()}; + if (this.mdRepresentation.browseDefinition.metadataBrowse) { + return {value: this.mdRepresentation.getValue()}; } return queryParams; } 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.spec.ts b/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.spec.ts index f0cc150b3e..dc8febe84a 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.spec.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/metadata-representation-list-element.component.spec.ts @@ -36,7 +36,7 @@ describe('MetadataRepresentationListElementComponent', () => { describe('when the value is not a URL', () => { beforeEach(() => { - comp.metadataRepresentation = mockMetadataRepresentation; + comp.mdRepresentation = mockMetadataRepresentation; }); it('isLink correctly detects a non-URL string as false', () => { waitForAsync(() => { @@ -47,7 +47,7 @@ describe('MetadataRepresentationListElementComponent', () => { describe('when the value is a URL', () => { beforeEach(() => { - comp.metadataRepresentation = mockMetadataRepresentationUrl; + comp.mdRepresentation = mockMetadataRepresentationUrl; }); it('isLink correctly detects a URL string as true', () => { waitForAsync(() => { 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 b69f6b37dc..d8f8621ca6 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,10 +10,15 @@ 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; /** * Returns true if this component's value matches a basic regex "Is this an HTTP URL" test @@ -20,7 +26,7 @@ export class MetadataRepresentationListElementComponent { isLink(): boolean { // Match any string that begins with http:// or https:// const linkPattern = new RegExp(/^https?\/\/.*/); - return linkPattern.test(this.metadataRepresentation.getValue()); + return linkPattern.test(this.mdRepresentation.getValue()); } } 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 7b611a7d1f..7d416e9f3e 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,17 +1,17 @@ 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 cfb812a475..91d7db3562 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 @@ -29,7 +29,7 @@ describe('PlainTextMetadataListElementComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(PlainTextMetadataListElementComponent); comp = fixture.componentInstance; - comp.metadataRepresentation = mockMetadataRepresentation; + comp.mdRepresentation = mockMetadataRepresentation; fixture.detectChanges(); })); diff --git a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts index 2d21a7afe8..4ff16e949a 100644 --- a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts +++ b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts @@ -20,9 +20,9 @@ export class PlainTextMetadataListElementComponent extends MetadataRepresentatio * expects 'startsWith' (eg browse by date) or 'value' (eg browse by title) */ getQueryParams() { - let queryParams = {startsWith: this.metadataRepresentation.getValue()}; - if (this.metadataRepresentation.browseDefinition.metadataBrowse) { - return {value: this.metadataRepresentation.getValue()}; + let queryParams = {startsWith: this.mdRepresentation.getValue()}; + if (this.mdRepresentation.browseDefinition.metadataBrowse) { + return {value: this.mdRepresentation.getValue()}; } return queryParams; } 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 18c03d71c4..f92ad28973 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 @@ -19,7 +19,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'; import { Context } from '../../../../core/shared/context.model'; @Component({ @@ -99,7 +99,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 b3b3bd2b5a..c69ef05380 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 @@ -20,7 +20,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'; import { Context } from '../../../../core/shared/context.model'; /** @@ -109,6 +109,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); + } } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 54d3cb2e66..5245f244c4 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -50,6 +50,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 { ThemedSearchFormComponent } from './search-form/themed-search-form.component'; import { @@ -348,6 +349,7 @@ const COMPONENTS = [ PageWithSidebarComponent, SidebarDropdownComponent, ThumbnailComponent, + ThemedThumbnailComponent, MyDSpaceStatusBadgeComponent, ThemedMyDSpaceStatusBadgeComponent, ViewModeSwitchComponent, diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 07e868c512..6b0727ccdd 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -12,8 +12,8 @@ import { HostBinding, ElementRef, } from '@angular/core'; -import { hasValue, isNotEmpty } from '../empty.util'; -import { from as fromPromise, Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs'; +import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { combineLatest, from as fromPromise, Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs'; import { ThemeService } from './theme.service'; import { catchError, switchMap, map, tap } from 'rxjs/operators'; import { GenericConstructor } from '../../core/shared/generic-constructor'; @@ -35,6 +35,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges */ public compRef$: BehaviorSubject> = new BehaviorSubject(undefined); + protected lazyLoadObs: Observable; protected lazyLoadSub: Subscription; protected themeSub: Subscription; @@ -58,20 +59,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 (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); + 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 { @@ -79,33 +84,49 @@ 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( + 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( tap(() => this.usedTheme = BASE_THEME_NAME), - map((unthemedFile: any) => { - return unthemedFile[this.getComponentName()]; - }) - ); - } - }), - ).subscribe((constructor: GenericConstructor) => { + 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, undefined, undefined, [this.themedElementContent.nativeElement.childNodes]); - this.connectInputsAndOutputs(); + if (hasValue(simpleChanges)) { + this.ngOnChanges(simpleChanges); + } else { + this.connectInputsAndOutputs(); + } this.compRef$.next(this.compRef); this.cdr.markForCheck(); this.themedElementContent.nativeElement.remove(); 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 9bf4eb1bcb..8999853d72 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'); + } + +} diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index 29aebe03fc..ebecb5e075 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -134,7 +134,7 @@ describe('ThumbnailComponent', () => { const img = fixture.debugElement.query(By.css('img.thumbnail-content')); img.nativeNode.onerror = null; - comp.ngOnChanges(); + comp.ngOnChanges({}); setSrcSpy = spyOn(comp, 'setSrc').and.callThrough(); }); @@ -261,14 +261,14 @@ describe('ThumbnailComponent', () => { describe('if content can be loaded', () => { it('should display an image', () => { - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement; expect(image.getAttribute('src')).toBe(thumbnail._links.content.href); }); it('should include the alt text', () => { - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement; expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); @@ -301,14 +301,14 @@ describe('ThumbnailComponent', () => { describe('if content can be loaded', () => { it('should display an image', () => { - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('src')).toBe(thumbnail._links.content.href); }); it('should display the alt text', () => { - comp.ngOnChanges(); + comp.ngOnChanges({}); fixture.detectChanges(); const image: HTMLElement = de.query(By.css('img')).nativeElement; expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); @@ -327,7 +327,7 @@ describe('ThumbnailComponent', () => { it('should show the default image', () => { comp.defaultImage = 'default/image.jpg'; - comp.ngOnChanges(); + comp.ngOnChanges({}); expect(comp.src$.getValue()).toBe('default/image.jpg'); }); }); diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index 5a7557def1..7f6531cc5e 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 { hasNoValue, hasValue } from '../shared/empty.util'; import { RemoteData } from '../core/data/remote-data'; @@ -70,8 +70,9 @@ 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 (hasNoValue(this.thumbnail)) { + this.setSrc(this.defaultImage); return; } 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/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index f57f6fb11a..adf3a888c1 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -140,6 +140,7 @@ import { } from './app/item-page/media-viewer/media-viewer-video/media-viewer-video.component'; import { NgxGalleryModule } from '@kolkov/ngx-gallery'; import { WorkspaceItemsDeletePageComponent } from './app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component'; +import { ThumbnailComponent } from './app/thumbnail/thumbnail.component'; const DECLARATIONS = [ FileSectionComponent, @@ -215,6 +216,7 @@ const DECLARATIONS = [ MediaViewerImageComponent, MediaViewerVideoComponent, WorkspaceItemsDeletePageComponent, + ThumbnailComponent, ]; @NgModule({