Amend VirtualScroller

This commit is contained in:
2025-04-30 15:52:46 +02:00
parent ea27f1e23d
commit 6756678b1d
6 changed files with 144 additions and 92 deletions

View File

@@ -0,0 +1,17 @@
<div #container class="card flex justify-center">
hi there!
<p-virtualscroller
[items]="items"
[itemSize]="[50, 100]"
orientation="both"
styleClass="border border-surface"
[autoSize]="true"
[style]="{ width: '400px', height: '200px' }"
>
<ng-template pTemplate="item" let-item let-options="options">
<div class="flex items-center p-2" [ngClass]="{ 'bg-surface-100 dark:bg-surface-700': options.odd }" style="height: 50px;">
<div *ngFor="let el of item" style="width: 100px">{{ el }}</div>
</div>
</ng-template>
</p-virtualscroller>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GridItemsComponent } from './grid-items.component';
describe('GridItemsComponent', () => {
let component: GridItemsComponent;
let fixture: ComponentFixture<GridItemsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GridItemsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GridItemsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,33 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {Scroller, ScrollerModule} from 'primeng/scroller';
import {NgClass, NgForOf, NgStyle} from '@angular/common';
@Component({
selector: 'app-grid-items',
imports: [
ScrollerModule,
NgClass,
NgForOf,
NgStyle
],
styles: [
`:host ::ng-deep {
.p-scroller-viewport {
flex: none;
}
}`
],
templateUrl: './grid-items.component.html',
styleUrl: './grid-items.component.css'
})
export class GridItemsComponent implements OnInit {
@ViewChild('container') containerRef!: ElementRef<HTMLElement>;
private readonly FONT_SIZE_PX = 14;
items!: string[][];
widthTest: string = '300px';
ngOnInit() {
this.items = Array.from({ length: 1000 }).map((_, i) => Array.from({ length: 1000 }).map((_j, j) => `Item #${i}_${j}`));
}
}

View File

@@ -1,60 +1,55 @@
<div #container class="h-full w-screen"> <div #container class="h-full w-screen">
@if (initialized) { @if (this.initialized) {
<p-virtualscroller <p-virtualscroller
[items]="cards" [items]="cardRows"
[itemSize]="itemSize" [itemSize]="[itemWidthInPx,itemHeightInPx]"
[lazy]="true" [lazy]="true"
[loading]="lazyLoading" [loading]="lazyLoading"
[appendOnly]="true" [appendOnly]="true"
[step]="pageSize"
[numToleratedItems]="numberOfToleratedItems"
(onLazyLoad)="onLazyLoad($event)" (onLazyLoad)="onLazyLoad($event)"
scrollHeight="600px" orientation="both"
scrollWidth="600px" [style]="{ width: '100vw', height: '100%' }"
> >
<ng-template <ng-template #item let-item let-options="options">
[appVirtualScroll]="cards" <div class="flex justify-around">
pTemplate="content" <p-card
let-options="options" *ngFor="let card of item"
> styleClass="w-[24rem] h-[36rem] m-2 overflow-hidden"
<div class="flex-none flex-wrap justify-around"> >
@for (card of options.items; track card.id) { <ng-template pTemplate="header">
<p-card styleClass="w-[24rem] h-[36rem] m-2 overflow-hidden"> <div
<ng-template pTemplate="header"> class="relative duration-200 w-[23rem] rounded-lg ease-in-out h-64 z-10 overflow-hidden hover:h-[34rem] hover:translate-y-2 m-2 after:absolute after:inset-0 after:shadow-[inset_0_-10px_30px_15px_rgba(0,0,0,0.85)] after:content-[''] hover:inset-0"
<div >
class="relative duration-200 w-[23rem] rounded-lg ease-in-out h-64 z-10 overflow-hidden hover:h-[34rem] hover:translate-y-2 m-2 after:absolute after:inset-0 after:shadow-[inset_0_-10px_30px_15px_rgba(0,0,0,0.85)] after:content-[''] hover:inset-0" <div class="absolute -inset-0">
> <!--suppress AngularNgOptimizedImage -->
<div class="absolute -inset-0"> <img
<!--suppress AngularNgOptimizedImage --> alt="Card"
<img class="w-full h-full object-cover object-center"
alt="Card" src="http://localhost:8080{{ card.imageApiPath }}"
class="w-full h-full object-cover object-center" />
src="http://localhost:8080{{ card.imageApiPath }}"
/>
</div>
</div> </div>
</ng-template> </div>
<ng-template pTemplate="title"> {{ card.name }}</ng-template> </ng-template>
<ng-template pTemplate="subtitle"> Card subtitle</ng-template> <ng-template pTemplate="title"> {{ card.name }}</ng-template>
<ng-template pTemplate="footer"> <ng-template pTemplate="subtitle"> Card subtitle</ng-template>
<div class="flex gap-4 mt-1"> <ng-template pTemplate="footer">
<p-button label="Cancel" severity="secondary" class="w-full" [outlined]="true" styleClass="w-full"/> <div class="flex gap-4 mt-1">
<p-button label="Save" class="w-full" styleClass="w-full"/> <p-button label="Cancel" severity="secondary" class="w-full" [outlined]="true" styleClass="w-full"/>
</div> <p-button label="Save" class="w-full" styleClass="w-full"/>
</ng-template> </div>
<p> </ng-template>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore sed consequuntur error repudiandae <p>
numquam deserunt Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore sed consequuntur error repudiandae
quisquam repellat libero asperiores earum nam nobis, culpa ratione quam perferendis esse, cupiditate numquam deserunt
neque quisquam repellat libero asperiores earum nam nobis, culpa ratione quam perferendis esse, cupiditate
quas! neque
</p> quas!
</p-card> </p>
} </p-card>
</div> </div>
</ng-template> </ng-template>
</p-virtualscroller> </p-virtualscroller>
} @else { } @else {
hold on im pogging rn hold on im poggin
} }
</div> </div>

View File

@@ -1,22 +1,22 @@
// noinspection DuplicatedCode // noinspection DuplicatedCode
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
import {Scroller, ScrollerLazyLoadEvent} from 'primeng/scroller'; import {Scroller, ScrollerLazyLoadEvent} from 'primeng/scroller';
import {Card as CardModel, CardService, PageDeck} from '../../openapi'; import {Card as CardModel, CardService} from '../../openapi';
import {BehaviorSubject, catchError, debounceTime, of, Subject, switchMap} from 'rxjs'; import {catchError, debounceTime, of, Subject, switchMap} from 'rxjs';
import {Button} from 'primeng/button'; import {Button} from 'primeng/button';
import {Card} from 'primeng/card'; import {Card} from 'primeng/card';
import {PrimeTemplate} from 'primeng/api'; import {PrimeTemplate} from 'primeng/api';
import {VirtualScrollDirective} from '../../directives/virtual-scroll.directive'; import {NgForOf} from '@angular/common';
@Component({ @Component({
selector: 'app-cards', selector: 'app-cards',
imports: [ imports: [
Scroller,
Button, Button,
Card, Card,
PrimeTemplate, PrimeTemplate,
Scroller, NgForOf
VirtualScrollDirective
], ],
templateUrl: './cards.component.html', templateUrl: './cards.component.html',
styleUrl: './cards.component.css' styleUrl: './cards.component.css'
@@ -29,21 +29,21 @@ export class CardsComponent implements AfterViewInit {
private readonly FONT_SIZE_PX = 14; private readonly FONT_SIZE_PX = 14;
private pageSubject = new BehaviorSubject<number>(0); private pageSubject = new Subject<number>();
private resizeObserver!: ResizeObserver; private resizeObserver!: ResizeObserver;
pageSize: number = 5;
page: number = 0; page: number = 0;
// noinspection PointlessArithmeticExpressionJS Card height + ( margin top/bottom) (rem) pageSize: number = 5;
rowHeight: number = 36 + (0.5 * 2); itemHeight: number = 36 + (0.5 * 2);
itemWidth: number = 24; // Card width (rem) itemWidth: number = 24;
cards: CardModel[] = []; itemHeightInPx: number = this.itemHeight * this.FONT_SIZE_PX;
lazyLoading: boolean = false; itemWidthInPx: number = this.itemWidth * this.FONT_SIZE_PX;
cardRows: CardModel[][] = [];
lazyLoading: boolean = true;
initialized: boolean = false;
itemsPerRow!: number; itemsPerRow!: number;
rowsInPage!: number; rowsInPage!: number;
itemSize: number = this.rowHeight; lastResponseSize?: number;
numberOfToleratedItems: number = 5;
initialized: boolean = false;
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.setupResizeObserver(); this.setupResizeObserver();
@@ -65,54 +65,38 @@ export class CardsComponent implements AfterViewInit {
) )
.subscribe(cards => { .subscribe(cards => {
this.lazyLoading = false; this.lazyLoading = false;
if (!this.lastResponseSize) {
this.lastResponseSize = cards.length;
}
if (!this.initialized) { if (!this.initialized) {
this.initialized = true; this.initialized = true;
} }
this.cards = [ this.cardRows[this.page] = cards
...this.cards, console.log(cards.length);
...cards
]
}) })
} }
private setupResizeObserver(): void { private setupResizeObserver(): void {
this.resizeObserver = new ResizeObserver(entries => { this.resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) { for (const entry of entries) {
this.calculateRowsInPage(entry.contentRect.height); this.rowsInPage = Math.ceil(entry.contentRect.height / this.itemHeightInPx);
this.calculateItemsPerRow(entry.contentRect.width); this.itemsPerRow = Math.floor(entry.contentRect.width / this.itemWidthInPx);
this.pageSize = this.itemsPerRow;
const itemsInViewPort = this.itemsPerRow * this.rowsInPage;
this.pageSize = itemsInViewPort;
this.numberOfToleratedItems = itemsInViewPort;
} }
}); });
this.resizeObserver.observe(this.containerRef.nativeElement); this.resizeObserver.observe(this.containerRef.nativeElement);
} }
private calculateItemsPerRow(containerWidth: number): void {
const newItemsPerRow = Math.floor(containerWidth / (this.itemWidth * this.FONT_SIZE_PX)) || 1;
if (newItemsPerRow !== this.itemsPerRow) {
this.itemsPerRow = newItemsPerRow;
}
this.updateVirtualScrollerSettings();
}
private calculateRowsInPage(containerHeight: number) {
this.rowsInPage = Math.ceil(containerHeight / (this.rowHeight * this.FONT_SIZE_PX));
}
private updateVirtualScrollerSettings(): void {
this.itemSize = (this.rowHeight * this.FONT_SIZE_PX) / this.itemsPerRow;
}
onLazyLoad(event: ScrollerLazyLoadEvent) { onLazyLoad(event: ScrollerLazyLoadEvent) {
if (!event || event.last % this.pageSize !== 0) { console.log(`kinda pogging rn cuz ${JSON.stringify(event)}`)
return;
}
this.pageSubject.next(++this.page); this.pageSubject.next(++this.page);
} }
protected readonly JSON = JSON;
} }