Files
dspace-angular/src/app/shared/entity-dropdown/entity-dropdown.component.ts
2024-08-06 15:52:15 +02:00

227 lines
6.0 KiB
TypeScript

import {
AsyncPipe,
NgFor,
NgIf,
} from '@angular/common';
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import {
BehaviorSubject,
Observable,
Subscription,
} from 'rxjs';
import {
reduce,
startWith,
switchMap,
} from 'rxjs/operators';
import { EntityTypeDataService } from '../../core/data/entity-type-data.service';
import { FindListOptions } from '../../core/data/find-list-options.model';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { RemoteData } from '../../core/data/remote-data';
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
import { getFirstSucceededRemoteWithNotEmptyData } from '../../core/shared/operators';
import { hasValue } from '../empty.util';
import { ThemedLoadingComponent } from '../loading/themed-loading.component';
@Component({
selector: 'ds-entity-dropdown',
templateUrl: './entity-dropdown.component.html',
styleUrls: ['./entity-dropdown.component.scss'],
standalone: true,
imports: [InfiniteScrollModule, NgIf, NgFor, ThemedLoadingComponent, AsyncPipe, TranslateModule],
})
export class EntityDropdownComponent implements OnInit, OnDestroy {
/**
* The entity list obtained from a search
* @type {Observable<ItemType[]>}
*/
public searchListEntity$: Observable<ItemType[]>;
/**
* A boolean representing if dropdown list is scrollable to the bottom
* @type {boolean}
*/
private scrollableBottom = false;
/**
* A boolean representing if dropdown list is scrollable to the top
* @type {boolean}
*/
private scrollableTop = false;
/**
* The list of entity to render
*/
public searchListEntity: ItemType[] = [];
/**
* TRUE if the parent operation is a 'new submission' operation, FALSE otherwise (eg.: is an 'Import metadata from an external source' operation).
*/
@Input() isSubmission: boolean;
/**
* The entity to output to the parent component
*/
@Output() selectionChange = new EventEmitter<ItemType>();
/**
* A boolean representing if the loader is visible or not
*/
public isLoadingList: BehaviorSubject<boolean> = new BehaviorSubject(false);
/**
* A numeric representig current page
*/
public currentPage: number;
/**
* A boolean representing if exist another page to render
*/
public hasNextPage: boolean;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
public subs: Subscription[] = [];
/**
* Initialize instance variables
*
* @param {ChangeDetectorRef} changeDetectorRef
* @param {EntityTypeDataService} entityTypeService
* @param el
*/
constructor(
private changeDetectorRef: ChangeDetectorRef,
private entityTypeService: EntityTypeDataService,
private el: ElementRef,
) { }
/**
* Method called on mousewheel event, it prevent the page scroll
* when arriving at the top/bottom of dropdown menu
*
* @param event
* mousewheel event
*/
@HostListener('mousewheel', ['$event']) onMousewheel(event) {
if (event.wheelDelta > 0 && this.scrollableTop) {
event.preventDefault();
}
if (event.wheelDelta < 0 && this.scrollableBottom) {
event.preventDefault();
}
}
/**
* Initialize entity list
*/
ngOnInit() {
this.resetPagination();
this.populateEntityList(this.currentPage);
}
/**
* Check if dropdown scrollbar is at the top or bottom of the dropdown list
*
* @param event
*/
public onScroll(event) {
this.scrollableBottom = (event.target.scrollTop + event.target.clientHeight === event.target.scrollHeight);
this.scrollableTop = (event.target.scrollTop === 0);
}
/**
* Method used from infitity scroll for retrieve more data on scroll down
*/
public onScrollDown() {
if ( this.hasNextPage ) {
this.populateEntityList(++this.currentPage);
}
}
/**
* Emit a [selectionChange] event when a new entity is selected from list
*
* @param event
* the selected [ItemType]
*/
public onSelect(event: ItemType) {
this.selectionChange.emit(event);
}
/**
* Method called for populate the entity list
* @param page page number
*/
public populateEntityList(page: number) {
this.isLoadingList.next(true);
// Set the pagination info
const findOptions: FindListOptions = {
elementsPerPage: 10,
currentPage: page,
};
let searchListEntity$;
if (this.isSubmission) {
searchListEntity$ = this.entityTypeService.getAllAuthorizedRelationshipType(findOptions);
} else {
searchListEntity$ = this.entityTypeService.getAllAuthorizedRelationshipTypeImport(findOptions);
}
this.searchListEntity$ = searchListEntity$.pipe(
getFirstSucceededRemoteWithNotEmptyData(),
switchMap((entityType: RemoteData<PaginatedList<ItemType>>) => {
if ( (this.searchListEntity.length + findOptions.elementsPerPage) >= entityType.payload.totalElements ) {
this.hasNextPage = false;
}
return entityType.payload.page;
}),
reduce((acc: any, value: any) => [...acc, value], []),
startWith([]),
);
this.subs.push(
this.searchListEntity$.subscribe(
(next) => { this.searchListEntity.push(...next); }, undefined,
() => { this.hideShowLoader(false); this.changeDetectorRef.detectChanges(); },
),
);
}
/**
* Reset pagination values
*/
public resetPagination() {
this.currentPage = 1;
this.hasNextPage = true;
this.searchListEntity = [];
}
/**
* Hide/Show the entity list loader
* @param hideShow true for show, false otherwise
*/
public hideShowLoader(hideShow: boolean) {
this.isLoadingList.next(hideShow);
}
/**
* Unsubscribe from all subscriptions
*/
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
}