common-ui/src/lib/listing/table-content/table-content.component.ts

116 lines
4.5 KiB
TypeScript

/* 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<Class extends IListable<PrimaryKey>, 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<string, (e: Class) => boolean>;
@Input() selectionEnabled!: boolean;
@Input() rowIdPrefix: string = 'item';
@Input() namePropertyKey?: string;
readonly trackBy = trackByFactory<Class>();
@ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport;
@ViewChild(HasScrollbarDirective, { static: true }) readonly hasScrollbarDirective!: HasScrollbarDirective;
constructor(
@Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent<Class>,
readonly listingService: ListingService<Class>,
@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<string, boolean> {
const classes: Record<string, boolean> = {
'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);
}
}