Use cdk-virtual-scroll for long cipher lists (#1001)

* Use cdk-virtual-scroll for cipher lists

* add trackBy, reorder dom

* Undo merge conflict error

* Fix layout, increase scrolling buffer

* fix linting

* Remove unused infinite-scroll directives for Send

* Add back refresh method

* Update jslib

* Fix itemSize and min/maxBufferPx directives

* Move refresh() into base class

* Use cipherListVirtualScroll strategy

* fix linting

* Update to use latest virtual-scroll strategy

* Update jslib
This commit is contained in:
Thomas Rittson 2021-08-11 13:00:04 +10:00 committed by GitHub
parent 157d9478d4
commit 816249a48a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 38 additions and 28 deletions

2
jslib

@ -1 +1 @@
Subproject commit 23309d33e2a335574ed898d6543040372d41526a Subproject commit c70c8ecc247cb92e1f867630031fd5cdf124bcd3

View File

@ -1,12 +1,12 @@
import 'zone.js/dist/zone'; import 'zone.js/dist/zone';
import { ToasterModule } from 'angular2-toaster'; import { ToasterModule } from 'angular2-toaster';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services.module'; import { ServicesModule } from './services.module';
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -36,6 +36,7 @@ import { ApiActionDirective } from 'jslib-angular/directives/api-action.directiv
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive'; import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive'; import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive'; import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive';
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive'; import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive'; import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive'; import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
@ -164,10 +165,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
BrowserModule, BrowserModule,
DragDropModule, DragDropModule,
FormsModule, FormsModule,
InfiniteScrollModule,
ReactiveFormsModule, ReactiveFormsModule,
ServicesModule, ServicesModule,
ToasterModule.forRoot(), ToasterModule.forRoot(),
ScrollingModule,
], ],
declarations: [ declarations: [
A11yTitleDirective, A11yTitleDirective,
@ -179,6 +180,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
BlurClickDirective, BlurClickDirective,
BoxRowDirective, BoxRowDirective,
CalloutComponent, CalloutComponent,
CipherListVirtualScroll,
CiphersComponent, CiphersComponent,
CollectionsComponent, CollectionsComponent,
ColorPasswordPipe, ColorPasswordPipe,

View File

@ -39,8 +39,7 @@
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div class="list" *ngIf="filteredSends.length" infiniteScroll [infiniteScrollDistance]="1" <div class="list" *ngIf="filteredSends.length">
[infiniteScrollContainer]="'#items .content'" [fromRoot]="true" (scrolled)="loadMore()">
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)" <a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"
title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)" title="{{'viewItem' | i18n}}" (contextmenu)="viewSendMenu(s)"
[ngClass]="{'active': s.id === sendId}" class="flex-list-item"> [ngClass]="{'active': s.id === sendId}" class="flex-list-item">

View File

@ -6,13 +6,11 @@
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<ng-container *ngIf="(isPaging() ? pagedCiphers : ciphers) as filteredCiphers"> <cdk-virtual-scroll-viewport itemSize="42" minBufferPx="400" maxBufferPx="600" *ngIf="ciphers.length">
<div class="list" *ngIf="filteredCiphers.length" infiniteScroll [infiniteScrollDistance]="1" <div class="list">
[infiniteScrollContainer]="'#items .content'" [fromRoot]="true" [infiniteScrollDisabled]="!isPaging()" <a *cdkVirtualFor="let c of ciphers; trackBy: trackByFn" appStopClick (click)="selectCipher(c)"
(scrolled)="loadMore()">
<a *ngFor="let c of filteredCiphers" appStopClick (click)="selectCipher(c)"
(contextmenu)="rightClickCipher(c)" href="#" title="{{'viewItem' | i18n}}" (contextmenu)="rightClickCipher(c)" href="#" title="{{'viewItem' | i18n}}"
[ngClass]="{'active': c.id === activeCipherId}" class="flex-list-item"> [ngClass]="{'active': c.id === activeCipherId}" class="flex-list-item virtual-scroll-item">
<app-vault-icon [cipher]="c"></app-vault-icon> <app-vault-icon [cipher]="c"></app-vault-icon>
<div class="flex-cipher-list-item"> <div class="flex-cipher-list-item">
<span class="text"> <span class="text">
@ -30,7 +28,8 @@
</div> </div>
</a> </a>
</div> </div>
<div class="no-items" *ngIf="!filteredCiphers.length"> </cdk-virtual-scroll-viewport>
<div class="no-items" *ngIf="!ciphers.length">
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i> <i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
<ng-container *ngIf="loaded"> <ng-container *ngIf="loaded">
<i class="fa fa-frown-o fa-4x" aria-hidden="true"></i> <i class="fa fa-frown-o fa-4x" aria-hidden="true"></i>
@ -38,7 +37,6 @@
<button (click)="addCipher()" class="btn block primary link">{{'addItem' | i18n}}</button> <button (click)="addCipher()" class="btn block primary link">{{'addItem' | i18n}}</button>
</ng-container> </ng-container>
</div> </div>
</ng-container>
</div> </div>
<div class="footer"> <div class="footer">
<button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()" <button appBlurClick (click)="addCipher()" (contextmenu)="addCipherOptions()"

View File

@ -1,16 +1,15 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component'; import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
import { CipherView } from 'jslib-common/models/view/cipherView';
@Component({ @Component({
selector: 'app-vault-ciphers', selector: 'app-vault-ciphers',
templateUrl: 'ciphers.component.html', templateUrl: 'ciphers.component.html',
}) })
export class CiphersComponent extends BaseCiphersComponent { export class CiphersComponent extends BaseCiphersComponent {
constructor(searchService: SearchService) { trackByFn(index: number, c: CipherView) {
super(searchService); return c.id;
this.pageSize = 250;
} }
} }

View File

@ -79,16 +79,16 @@ textarea {
resize: vertical; resize: vertical;
} }
div:not(.modal)::-webkit-scrollbar { div:not(.modal)::-webkit-scrollbar, .cdk-virtual-scroll-viewport::-webkit-scrollbar {
width: 10px; width: 10px;
height: 10px; height: 10px;
} }
div:not(.modal)::-webkit-scrollbar-track { div:not(.modal)::-webkit-scrollbar-track, .cdk-virtual-scroll-viewport::-webkit-scrollbar-track {
background-color: transparent; background-color: transparent;
} }
div:not(.modal)::-webkit-scrollbar-thumb { div:not(.modal)::-webkit-scrollbar-thumb, .cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
margin-right: 1px; margin-right: 1px;
@ -102,3 +102,15 @@ div:not(.modal)::-webkit-scrollbar-thumb {
} }
} }
} }
// cdk-virtual-scroll
.cdk-virtual-scroll-viewport {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.cdk-virtual-scroll-content-wrapper {
width: 100%;
}