1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-11 10:10:25 +01:00

[PM-8380] Browser Refresh - Virtual scrolling (#10646)

* [PM-8380] Add option to disable content padding for popup page

* [PM-8380] Add cdkVirtualScroll to vault list items and fixed item heights

* [PM-8380] Move item height constants to item component
This commit is contained in:
Shane Melton 2024-08-22 07:40:32 -07:00 committed by GitHub
parent 7da1082849
commit ade01c9d07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 79 additions and 47 deletions

View File

@ -17,7 +17,8 @@
[ngClass]="{ 'tw-invisible': loading }" [ngClass]="{ 'tw-invisible': loading }"
> >
<div <div
class="tw-max-w-screen-sm tw-mx-auto tw-p-3 tw-flex-1 tw-flex tw-flex-col tw-h-full tw-w-full" class="tw-max-w-screen-sm tw-mx-auto tw-flex-1 tw-flex tw-flex-col tw-h-full tw-w-full"
[ngClass]="{ 'tw-p-3': !disablePadding }"
> >
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>

View File

@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, Input, inject, signal } from "@angular/core"; import { booleanAttribute, Component, inject, Input, signal } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -17,6 +17,9 @@ export class PopupPageComponent {
@Input() loading = false; @Input() loading = false;
@Input({ transform: booleanAttribute })
disablePadding = false;
protected scrolled = signal(false); protected scrolled = signal(false);
isScrolled = this.scrolled.asReadonly(); isScrolled = this.scrolled.asReadonly();

View File

@ -17,47 +17,50 @@
{{ description }} {{ description }}
</div> </div>
<bit-item-group> <bit-item-group>
<bit-item *ngFor="let cipher of ciphers"> <cdk-virtual-scroll-viewport [itemSize]="ItemHeight">
<a <bit-item *cdkVirtualFor="let cipher of ciphers">
bit-item-content <a
(click)="onViewCipher(cipher)" bit-item-content
[appA11yTitle]="'viewItemTitle' | i18n: cipher.name" (click)="onViewCipher(cipher)"
> [appA11yTitle]="'viewItemTitle' | i18n: cipher.name"
<app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon> class="{{ ItemHeightClass }}"
<span data-testid="item-name">{{ cipher.name }}</span> >
<i <app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon>
*ngIf="cipher.organizationId" <span data-testid="item-name">{{ cipher.name }}</span>
appOrgIcon <i
[tierType]="cipher.organization.productTierType" *ngIf="cipher.organizationId"
[size]="'small'" appOrgIcon
[appA11yTitle]="orgIconTooltip(cipher)" [tierType]="cipher.organization.productTierType"
></i> [size]="'small'"
<i [appA11yTitle]="orgIconTooltip(cipher)"
*ngIf="cipher.hasAttachments" ></i>
class="bwi bwi-paperclip bwi-sm" <i
[appA11yTitle]="'attachments' | i18n" *ngIf="cipher.hasAttachments"
></i> class="bwi bwi-paperclip bwi-sm"
<span slot="secondary">{{ cipher.subTitle }}</span> [appA11yTitle]="'attachments' | i18n"
</a> ></i>
<ng-container slot="end"> <span slot="secondary">{{ cipher.subTitle }}</span>
<bit-item-action *ngIf="showAutofillButton"> </a>
<button <ng-container slot="end">
type="button" <bit-item-action *ngIf="showAutofillButton">
bitBadge <button
variant="primary" type="button"
(click)="doAutofill(cipher)" bitBadge
[title]="'autofillTitle' | i18n: cipher.name" variant="primary"
[attr.aria-label]="'autofillTitle' | i18n: cipher.name" (click)="doAutofill(cipher)"
> [title]="'autofillTitle' | i18n: cipher.name"
{{ "autoFill" | i18n }} [attr.aria-label]="'autofillTitle' | i18n: cipher.name"
</button> >
</bit-item-action> {{ "autoFill" | i18n }}
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions> </button>
<app-item-more-options </bit-item-action>
[cipher]="cipher" <app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
[hideAutofillOptions]="showAutofillButton" <app-item-more-options
></app-item-more-options> [cipher]="cipher"
</ng-container> [hideAutofillOptions]="showAutofillButton"
</bit-item> ></app-item-more-options>
</ng-container>
</bit-item>
</cdk-virtual-scroll-viewport>
</bit-item-group> </bit-item-group>
</bit-section> </bit-section>

View File

@ -1,3 +1,4 @@
import { ScrollingModule } from "@angular/cdk/scrolling";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { booleanAttribute, Component, EventEmitter, Input, Output } from "@angular/core"; import { booleanAttribute, Component, EventEmitter, Input, Output } from "@angular/core";
import { Router, RouterLink } from "@angular/router"; import { Router, RouterLink } from "@angular/router";
@ -6,6 +7,8 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { import {
BadgeModule, BadgeModule,
BitItemHeight,
BitItemHeightClass,
ButtonModule, ButtonModule,
IconButtonModule, IconButtonModule,
ItemModule, ItemModule,
@ -35,12 +38,16 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options
ItemCopyActionsComponent, ItemCopyActionsComponent,
ItemMoreOptionsComponent, ItemMoreOptionsComponent,
OrgIconDirective, OrgIconDirective,
ScrollingModule,
], ],
selector: "app-vault-list-items-container", selector: "app-vault-list-items-container",
templateUrl: "vault-list-items-container.component.html", templateUrl: "vault-list-items-container.component.html",
standalone: true, standalone: true,
}) })
export class VaultListItemsContainerComponent { export class VaultListItemsContainerComponent {
protected ItemHeightClass = BitItemHeightClass;
protected ItemHeight = BitItemHeight;
/** /**
* The list of ciphers to display. * The list of ciphers to display.
*/ */

View File

@ -1,4 +1,4 @@
<popup-page [loading]="loading$ | async"> <popup-page [loading]="loading$ | async" disablePadding>
<popup-header slot="header" [pageTitle]="'vault' | i18n"> <popup-header slot="header" [pageTitle]="'vault' | i18n">
<ng-container slot="end"> <ng-container slot="end">
<app-new-item-dropdown [initialValues]="newItemItemValues$ | async"></app-new-item-dropdown> <app-new-item-dropdown [initialValues]="newItemItemValues$ | async"></app-new-item-dropdown>
@ -54,7 +54,7 @@
</div> </div>
</div> </div>
<ng-container *ngIf="vaultState === null"> <div *ngIf="vaultState === null" cdkVirtualScrollingElement class="tw-h-full tw-p-3">
<app-autofill-vault-list-items></app-autofill-vault-list-items> <app-autofill-vault-list-items></app-autofill-vault-list-items>
<app-vault-list-items-container <app-vault-list-items-container
[title]="'favorites' | i18n" [title]="'favorites' | i18n"
@ -67,6 +67,6 @@
id="allItems" id="allItems"
disableSectionMargin disableSectionMargin
></app-vault-list-items-container> ></app-vault-list-items-container>
</ng-container> </div>
</ng-container> </ng-container>
</popup-page> </popup-page>

View File

@ -1,3 +1,4 @@
import { ScrollingModule } from "@angular/cdk/scrolling";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
@ -51,6 +52,7 @@ enum VaultState {
RouterLink, RouterLink,
VaultV2SearchComponent, VaultV2SearchComponent,
NewItemDropdownV2Component, NewItemDropdownV2Component,
ScrollingModule,
], ],
providers: [VaultUiOnboardingService], providers: [VaultUiOnboardingService],
}) })

View File

@ -1 +1,3 @@
export * from "./item.module"; export * from "./item.module";
export { BitItemHeight, BitItemHeightClass } from "./item.component";

View File

@ -5,6 +5,20 @@ import { A11yRowDirective } from "../a11y/a11y-row.directive";
import { ItemActionComponent } from "./item-action.component"; import { ItemActionComponent } from "./item-action.component";
/**
* The class used to set the height of a bit item's inner content.
*/
export const BitItemHeightClass = `tw-h-[52px]`;
/**
* The height of a bit item in pixels. Includes any margin, padding, or border. Used by the virtual scroll
* to estimate how many items can be displayed at once and how large the virtual container should be.
* Needs to be updated if the item height or spacing changes.
*
* 52px + 5.25px bottom margin + 1px border = 58.25px
*/
export const BitItemHeight = 58.25; //
@Component({ @Component({
selector: "bit-item", selector: "bit-item",
standalone: true, standalone: true,