/* eslint-disable @angular-eslint/prefer-on-push-component-change-detection */ import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { AsyncPipe, NgClass } from '@angular/common'; import { AfterViewInit, Component, forwardRef, HostListener, Inject, Input, OnDestroy, Optional, ViewChild } from '@angular/core'; import { RouterLink } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; import { delay, tap } from 'rxjs/operators'; import { HasScrollbarDirective } from '../../directives/has-scrollbar.directive'; import { HelpModeService } from '../../help-mode/help-mode.service'; import { SnakeCasePipe } from '../../pipes/snake-case.pipe'; import { AutoUnsubscribe } from '../../utils/auto-unsubscribe.directive'; import { trackByFactory } from '../../utils/functions'; import { ListingComponent } from '../listing-component.directive'; import { IListable } from '../models/listable'; import { Id } from '../models/trackable'; import { ListingService } from '../services/listing.service'; import { TableItemComponent } from './table-item/table-item.component'; @Component({ selector: 'iqser-table-content', templateUrl: './table-content.component.html', styleUrls: ['./table-content.component.scss'], standalone: true, imports: [ CdkVirtualScrollViewport, AsyncPipe, CdkFixedSizeVirtualScroll, HasScrollbarDirective, CdkVirtualForOf, SnakeCasePipe, NgClass, RouterLink, TableItemComponent, ], }) export class TableContentComponent, PrimaryKey extends Id = Class['id']> extends AutoUnsubscribe implements OnDestroy, AfterViewInit { private _lastScrolledIndex = 0; private _multiSelectActive$ = new BehaviorSubject(false); @Input() itemSize!: number; @Input() itemMouseEnterFn?: (entity: Class) => void; @Input() itemMouseLeaveFn?: (entity: Class) => void; @Input() tableItemClasses?: Record boolean>; @Input() selectionEnabled!: boolean; @Input() rowIdPrefix: string = 'item'; @Input() namePropertyKey?: string; readonly trackBy = trackByFactory(); @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; @ViewChild(HasScrollbarDirective, { static: true }) readonly hasScrollbarDirective!: HasScrollbarDirective; constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, readonly listingService: ListingService, @Optional() readonly helpModeService: HelpModeService, ) { super(); this.addSubscription = this.listingComponent.noContent$.subscribe(() => { setTimeout(() => { this.scrollViewport?.checkViewportSize(); }, 0); }); } multiSelect(entity: Class, $event: MouseEvent): void { if (this.selectionEnabled && this._multiSelectActive$.value) { $event.stopPropagation(); this.listingService.select(entity, $event.shiftKey); } } ngAfterViewInit(): void { this.addSubscription = this.scrollViewport.scrolledIndexChange.pipe(tap(index => (this._lastScrolledIndex = index))).subscribe(); this.addSubscription = this.listingService.displayedLength$ .pipe( delay(100), tap(() => this.hasScrollbarDirective.process()), ) .subscribe(); } scrollToLastIndex(): void { this.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth'); } getTableItemClasses(entity: Class): Record { const classes: Record = { 'table-item': true, 'cursor-default': !entity.routerLink, }; for (const key in this.tableItemClasses) { if (Object.prototype.hasOwnProperty.call(this.tableItemClasses, key)) { classes[key] = this.tableItemClasses[key](entity); } } return classes; } @HostListener('window:keydown.control') @HostListener('window:keydown.meta') @HostListener('window:keydown.shift') private _enableMultiSelect() { this._multiSelectActive$.next(true); } @HostListener('window:focus') @HostListener('window:blur') @HostListener('window:keyup.control') @HostListener('window:keyup.meta') @HostListener('window:keyup.shift') private _disableMultiSelect() { this._multiSelectActive$.next(false); } }