From b8e353d51c5fb58d286152dd2b36bea41a76b84f Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 4 Jan 2024 17:16:21 +0100 Subject: [PATCH] add tabulatable loader missing files --- ...bjects-collection-tabulatable.component.ts | 83 +++++++ .../tabulatable-objects-loader.component.html | 1 + .../tabulatable-objects-loader.component.scss | 3 + ...bulatable-objects-loader.component.spec.ts | 23 ++ .../tabulatable-objects-loader.component.ts | 207 ++++++++++++++++++ 5 files changed, 317 insertions(+) create mode 100644 src/app/shared/object-collection/shared/objects-collection-tabulatable/objects-collection-tabulatable.component.ts create mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.html create mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.scss create mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.spec.ts create mode 100644 src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.ts diff --git a/src/app/shared/object-collection/shared/objects-collection-tabulatable/objects-collection-tabulatable.component.ts b/src/app/shared/object-collection/shared/objects-collection-tabulatable/objects-collection-tabulatable.component.ts new file mode 100644 index 0000000000..57b7c39771 --- /dev/null +++ b/src/app/shared/object-collection/shared/objects-collection-tabulatable/objects-collection-tabulatable.component.ts @@ -0,0 +1,83 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ListableObject } from "../listable-object.model"; +import { CollectionElementLinkType } from "../../collection-element-link.type"; +import { Context } from "../../../../core/shared/context.model"; +import { ViewMode } from "../../../../core/shared/view-mode.model"; +import { DSpaceObject } from "../../../../core/shared/dspace-object.model"; +import { DSONameService } from "../../../../core/breadcrumbs/dso-name.service"; +import { RemoteData } from "../../../../core/data/remote-data"; +import { PaginatedList } from "../../../../core/data/paginated-list.model"; + +@Component({ + selector: 'ds-objects-collection-tabulatable', + template: ``, +}) +export class AbstractTabulatableElementComponent> { + + /** + * The object to render in this list element + */ + @Input() objects: T; + + /** + * The link type to determine the type of link rendered in this element + */ + @Input() linkType: CollectionElementLinkType; + + /** + * The identifier of the list this element resides in + */ + @Input() listID: string; + + /** + * The value to display for this element + */ + @Input() value: string; + + /** + * Whether to show the badge label or not + */ + @Input() showLabel = true; + + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + + /** + * The context we matched on to get this component + */ + @Input() context: Context; + + /** + * The viewmode we matched on to get this component + */ + @Input() viewMode: ViewMode; + + /** + * Emit when the object has been reloaded. + */ + @Output() reloadedObject = new EventEmitter>>(); + + /** + * The available link types + */ + linkTypes = CollectionElementLinkType; + + /** + * The available view modes + */ + viewModes = ViewMode; + + /** + * The available contexts + */ + contexts = Context; + + constructor( + public dsoNameService: DSONameService, + ) { + } + +} + diff --git a/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.html b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.html new file mode 100644 index 0000000000..ff51746cb9 --- /dev/null +++ b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.html @@ -0,0 +1 @@ + diff --git a/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.scss b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.scss new file mode 100644 index 0000000000..b9bc65ea45 --- /dev/null +++ b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.scss @@ -0,0 +1,3 @@ +:host { + width: 100%; +} diff --git a/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.spec.ts b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.spec.ts new file mode 100644 index 0000000000..59d0a42ecd --- /dev/null +++ b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TabulatableObjectsLoaderComponent } from './tabulatable-objects-loader.component'; + +describe('TabulatableObjectsComponent', () => { + let component: TabulatableObjectsLoaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TabulatableObjectsLoaderComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TabulatableObjectsLoaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.ts b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.ts new file mode 100644 index 0000000000..b3c83294b4 --- /dev/null +++ b/src/app/shared/object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component.ts @@ -0,0 +1,207 @@ +import { + ChangeDetectorRef, + Component, + ComponentRef, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + ViewChild +} from '@angular/core'; +import { ListableObject } from "../listable-object.model"; +import { ViewMode } from "../../../../core/shared/view-mode.model"; +import { Context } from "../../../../core/shared/context.model"; +import { CollectionElementLinkType } from "../../collection-element-link.type"; +import { combineLatest, Observable, of as observableOf, Subscription } from "rxjs"; +import { ThemeService } from "../../../theme-support/theme.service"; +import { hasNoValue, hasValue, isNotEmpty } from "../../../empty.util"; +import { take } from "rxjs/operators"; +import { GenericConstructor } from "../../../../core/shared/generic-constructor"; +import { TabulatableObjectsDirective } from "./tabulatable-objects.directive"; +import { PaginatedList } from "../../../../core/data/paginated-list.model"; +import { getTabulatableObjectsComponent } from "./tabulatable-objects.decorator"; + +@Component({ + selector: 'ds-tabulatable-objects-loader', + templateUrl: './tabulatable-objects-loader.component.html', + styleUrls: ['./tabulatable-objects-loader.component.scss'] +}) +/** + * Component for determining what component to use depending on the item's entity type (dspace.entity.type) + */ +export class TabulatableObjectsLoaderComponent implements OnInit, OnChanges, OnDestroy { + /** + * The items to determine the component for + */ + @Input() objects: PaginatedList; + + + /** + * The context of listable object + */ + @Input() context: Context; + + /** + * The type of link used to render the links inside the listable object + */ + @Input() linkType: CollectionElementLinkType; + + /** + * The identifier of the list this element resides in + */ + @Input() listID: string; + + /** + * Whether to show the badge label or not + */ + @Input() showLabel = true; + + /** + * Whether to show the thumbnail preview + */ + @Input() showThumbnails; + + /** + * The value to display for this element + */ + @Input() value: string; + + /** + * Directive hook used to place the dynamic child component + */ + @ViewChild(TabulatableObjectsDirective, { static: true }) tabulatableObjectsDirective: TabulatableObjectsDirective; + + /** + * Emit when the listable object has been reloaded. + */ + @Output() contentChange = new EventEmitter>(); + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subs: Subscription[] = []; + + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + + /** + * The view mode used to identify the components + */ + protected viewMode: ViewMode = ViewMode.Table; + + /** + * The list of input and output names for the dynamic component + */ + protected inAndOutputNames: string[] = [ + 'objects', + 'linkType', + 'listID', + 'showLabel', + 'showThumbnails', + 'context', + 'viewMode', + 'value', + 'hideBadges', + 'contentChange', + ]; + + constructor(private cdr: ChangeDetectorRef, private themeService: ThemeService) { + } + + /** + * Setup the dynamic child component + */ + ngOnInit(): void { + this.instantiateComponent(this.objects); + } + + /** + * Whenever the inputs change, update the inputs of the dynamic component + */ + ngOnChanges(changes: SimpleChanges): void { + if (hasNoValue(this.compRef)) { + // sometimes the component has not been initialized yet, so it first needs to be initialized + // before being called again + this.instantiateComponent(this.objects, changes); + } else { + // if an input or output has changed + if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + this.connectInputsAndOutputs(); + if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { + (this.compRef.instance as any).ngOnChanges(changes); + } + } + } + } + + ngOnDestroy() { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } + + private instantiateComponent(objects: PaginatedList, changes?: SimpleChanges): void { + // objects need to have same render type so we access just the first in the page + const component = this.getComponent(objects.page[0].getRenderTypes(), this.viewMode, this.context); + + const viewContainerRef = this.tabulatableObjectsDirective.viewContainerRef; + viewContainerRef.clear(); + + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined + } + ); + + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); + } + + if ((this.compRef.instance as any).reloadedObject) { + combineLatest([ + observableOf(changes), + (this.compRef.instance as any).reloadedObject.pipe(take(1)) as Observable>, + ]).subscribe(([simpleChanges, reloadedObjects]: [SimpleChanges, PaginatedList]) => { + if (reloadedObjects) { + this.compRef.destroy(); + this.objects = reloadedObjects; + this.instantiateComponent(reloadedObjects, simpleChanges); + this.cdr.detectChanges(); + this.contentChange.emit(reloadedObjects); + } + }); + } + } + + /** + * Fetch the component depending on the item's entity type, view mode and context + * @returns {GenericConstructor} + */ + getComponent(renderTypes: (string | GenericConstructor)[], + viewMode: ViewMode, + context: Context): GenericConstructor { + return getTabulatableObjectsComponent(renderTypes, viewMode, context, this.themeService.getThemeName()); + } + + /** + * Connect the in and outputs of this component to the dynamic component, + * to ensure they're in sync + */ + protected connectInputsAndOutputs(): void { + if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { + this.compRef.instance[name] = this[name]; + }); + } + } + +}