1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-31 08:43:54 +00:00

Duplicated '/vault-filters' into '/vault-v3'

This commit is contained in:
Leslie Xiong
2025-12-01 19:15:32 -05:00
parent d960ba1e45
commit 6bd297dee3
14 changed files with 782 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
<ng-container *ngIf="show">
<div class="filter-heading">
<h2>
<button
type="button"
class="no-btn"
[attr.aria-expanded]="!isCollapsed(collectionsGrouping)"
aria-controls="collection-filters"
(click)="toggleCollapse(collectionsGrouping)"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(collectionsGrouping),
'bwi-angle-down': !isCollapsed(collectionsGrouping),
}"
></i>
&nbsp;{{ collectionsGrouping.name | i18n }}
</button>
</h2>
</div>
<ul id="collection-filters" *ngIf="!isCollapsed(collectionsGrouping)" class="filter-options">
<ng-template #recursiveCollections let-collections>
<li
class="filter-option"
*ngFor="let c of collections"
[ngClass]="{ active: c.node.id === activeFilter.selectedCollectionId }"
>
<span class="filter-buttons">
<button
type="button"
*ngIf="c.children.length"
class="toggle-button"
[attr.aria-expanded]="!isCollapsed(c.node)"
[attr.aria-controls]="c.node.name + '_children'"
(click)="toggleCollapse(c.node)"
appA11yTitle="{{ 'toggleCollapse' | i18n }} {{ c.node.name }}"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(c.node),
'bwi-angle-down': !isCollapsed(c.node),
}"
></i>
</button>
<button
type="button"
class="filter-button"
(click)="applyFilter(c.node)"
[attr.aria-pressed]="c.node.id === activeFilter.selectedCollectionId"
[title]="c.node.name"
>
<i
*ngIf="c.children.length === 0"
[class]="
'bwi bwi-fw ' +
(c.node.type === DefaultCollectionType ? 'bwi-user' : 'bwi-collection-shared')
"
aria-hidden="true"
></i>
&nbsp;{{ c.node.name }}
</button>
</span>
<ul
[id]="c.node.name + '_children'"
class="nested-filter-options"
*ngIf="c.children.length && !isCollapsed(c.node)"
>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: c.children }"
>
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveCollections; context: { $implicit: nestedCollections }"
>
</ng-container>
</ul>
</ng-container>

View File

@@ -0,0 +1,12 @@
import { Component } from "@angular/core";
import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/collection-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-collection-filter",
templateUrl: "collection-filter.component.html",
standalone: false,
})
export class CollectionFilterComponent extends BaseCollectionFilterComponent {}

View File

@@ -0,0 +1,94 @@
<ng-container *ngIf="!hide">
<div class="filter-heading">
<h2>
<button
type="button"
class="toggle-button"
[attr.aria-expanded]="!isCollapsed(foldersGrouping)"
aria-controls="folder-filters"
(click)="toggleCollapse(foldersGrouping)"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(foldersGrouping),
'bwi-angle-down': !isCollapsed(foldersGrouping),
}"
></i>
&nbsp;{{ foldersGrouping.name | i18n }}
</button>
</h2>
<button
type="button"
class="add-button"
(click)="addFolder()"
appA11yTitle="{{ 'addFolder' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
</button>
</div>
<ul id="folder-filters" class="filter-options" *ngIf="!isCollapsed(foldersGrouping)">
<ng-template #recursiveFolders let-folders>
<li
*ngFor="let f of folders"
[ngClass]="{
active: f.node.id === activeFilter.selectedFolderId && activeFilter.selectedFolder,
}"
class="filter-option"
>
<span class="filter-buttons">
<button
type="button"
class="toggle-button"
*ngIf="f.children.length"
(click)="toggleCollapse(f.node)"
appA11yTitle="{{ 'toggleCollapse' | i18n }} {{ f.node.name }}"
[attr.aria-expanded]="!isCollapsed(f.node)"
[attr.aria-controls]="f.node.name + '_children'"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed(f.node),
'bwi-angle-down': !isCollapsed(f.node),
}"
></i>
</button>
<button
type="button"
class="filter-button"
(click)="applyFilter(f.node)"
[attr.aria-pressed]="
activeFilter.selectedFolder && f.node.id === activeFilter.selectedFolderId
"
>
<i *ngIf="f.children.length === 0" class="bwi bwi-fw bwi-folder" aria-hidden="true"></i>
&nbsp;{{ f.node.name }}
</button>
<button
type="button"
class="edit-button"
*ngIf="f.node.id"
(click)="editFolder(f.node)"
appA11yTitle="{{ 'editFolder' | i18n }}: {{ f.node.name }}"
>
<i class="bwi bwi-pencil bwi-fw" aria-hidden="true"></i>
</button>
</span>
<ul
[id]="f.node.name + '_children'"
class="nested-filter-options"
*ngIf="f.children.length && !isCollapsed(f.node)"
>
<ng-container *ngTemplateOutlet="recursiveFolders; context: { $implicit: f.children }">
</ng-container>
</ul>
</li>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveFolders; context: { $implicit: nestedFolders }"
></ng-container>
</ul>
</ng-container>

View File

@@ -0,0 +1,12 @@
import { Component } from "@angular/core";
import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/folder-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-folder-filter",
templateUrl: "folder-filter.component.html",
standalone: false,
})
export class FolderFilterComponent extends BaseFolderFilterComponent {}

View File

@@ -0,0 +1,140 @@
<ng-container *ngIf="show">
<ng-container [ngSwitch]="displayMode">
<ng-container *ngSwitchCase="'organizationDataOwnershipPolicy'">
<div class="filter-heading" [ngClass]="{ active: !hasActiveFilter }">
<button
type="button"
class="toggle-button"
appA11yTitle="{{ 'toggleCollapse' | i18n }} {{ organizationGrouping.name | i18n }}"
(click)="toggleCollapse()"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed,
}"
></i>
</button>
&nbsp;
<h2>
<button
type="button"
class="filter-button"
(click)="clearFilter()"
[attr.aria-pressed]="!hasActiveFilter"
appA11yTitle="{{ 'vault' | i18n }}: {{ organizationGrouping.name | i18n }}"
>
{{ organizationGrouping.name | i18n }}
</button>
</h2>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options no-margin">
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyOrganizationFilter(organization)"
[attr.aria-pressed]="activeFilter.myVaultOnly"
appA11yTitle="{{ 'vault' | i18n }}: {{ organization.name }}"
>
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
&nbsp;{{ organization.name }}
</button>
<span *ngIf="!organization.enabled" class="tw-ml-auto">
<i
class="bwi bwi-fw bwi-exclamation-triangle text-danger mr-auto"
attr.aria-label="{{ 'organizationIsDisabled' | i18n }}"
appA11yTitle="{{ 'organizationIsDisabled' | i18n }}"
></i>
</span>
</span>
</li>
</ul>
</ng-container>
<ng-container *ngSwitchDefault>
<div class="filter-heading" [ngClass]="{ active: !hasActiveFilter }">
<button
type="button"
appA11yTitle="{{ 'toggleCollapse' | i18n }} {{ organizationGrouping.name | i18n }}"
(click)="toggleCollapse()"
[attr.aria-expanded]="!isCollapsed"
aria-controls="organization-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed,
}"
></i>
</button>
&nbsp;
<h2>
<button
type="button"
class="filter-button"
(click)="clearFilter()"
[attr.aria-pressed]="!hasActiveFilter"
appA11yTitle="{{ 'vault' | i18n }}: {{ organizationGrouping.name | i18n }}"
>
{{ organizationGrouping.name | i18n }}
</button>
</h2>
</div>
<ul id="organization-filters" *ngIf="!isCollapsed" class="filter-options no-margin">
<li class="filter-option" [ngClass]="{ active: activeFilter.myVaultOnly }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyMyVaultFilter()"
[attr.aria-pressed]="activeFilter.myVaultOnly"
appA11yTitle="{{ 'vault' | i18n }}: {{ 'myVault' | i18n }}"
>
<i class="bwi bwi-fw bwi-user" aria-hidden="true"></i>
&nbsp;{{ "myVault" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
*ngFor="let organization of organizations"
[ngClass]="{ active: organization.id === activeFilter.selectedOrganizationId }"
>
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyOrganizationFilter(organization)"
appA11yTitle="{{ 'vault' | i18n }}: {{ organization.name }} {{
organization.enabled ? '' : '(' + ('organizationIsDisabled' | i18n) + ')'
}}"
[attr.aria-pressed]="activeFilter.selectedOrganizationId === organization.id"
>
<i class="bwi bwi-fw bwi-business" aria-hidden="true"></i>
&nbsp;{{ organization.name }}
</button>
<span *ngIf="!organization.enabled" class="tw-ml-auto">
<i
class="bwi bwi-fw bwi-exclamation-triangle text-danger mr-auto"
attr.aria-label="{{ 'organizationIsDisabled' | i18n }}"
appA11yTitle="{{ 'organizationIsDisabled' | i18n }}"
></i>
</span>
</span>
</li>
</ul>
</ng-container>
</ng-container>
<hr />
</ng-container>

View File

@@ -0,0 +1,53 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component } from "@angular/core";
import { OrganizationFilterComponent as BaseOrganizationFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/organization-filter.component";
import { DisplayMode } from "@bitwarden/angular/vault/vault-filter/models/display-mode";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-organization-filter",
templateUrl: "organization-filter.component.html",
standalone: false,
})
export class OrganizationFilterComponent extends BaseOrganizationFilterComponent {
get show() {
const hiddenDisplayModes: DisplayMode[] = [
"singleOrganizationAndOrganizatonDataOwnershipPolicies",
];
return (
!this.hide &&
this.organizations.length > 0 &&
hiddenDisplayModes.indexOf(this.displayMode) === -1
);
}
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
) {
super();
}
async applyOrganizationFilter(organization: Organization) {
if (organization.enabled) {
//proceed with default behaviour for enabled organizations
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
super.applyOrganizationFilter(organization);
} else {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("disabledOrganizationFilterError"),
});
}
}
}

View File

@@ -0,0 +1,68 @@
<ng-container *ngIf="show">
<h2 class="sr-only">{{ "filters" | i18n }}</h2>
<ul class="filter-options">
<li class="filter-option" [ngClass]="{ active: activeFilter.status === 'all' }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyFilter('all')"
[attr.aria-pressed]="activeFilter.status === 'all'"
>
<i class="bwi bwi-fw bwi-filter" aria-hidden="true"></i>&nbsp;{{ "allItems" | i18n }}
</button>
</span>
</li>
<li
class="filter-option"
*ngIf="!hideFavorites"
[ngClass]="{ active: activeFilter.status === 'favorites' }"
>
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyFilter('favorites')"
[attr.aria-pressed]="activeFilter.status === 'favorites'"
>
<i class="bwi bwi-fw bwi-star" aria-hidden="true"></i>&nbsp;{{ "favorites" | i18n }}
</button>
</span>
</li>
<li
class="filter-option tw-flex tw-items-center tw-gap-2 [&>span]:tw-w-min"
[ngClass]="{ active: activeFilter.status === 'archive' }"
*ngIf="!hideArchive"
>
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="handleArchiveFilter($event)"
[attr.aria-pressed]="activeFilter.status === 'archive'"
>
<i class="bwi bwi-fw bwi-archive" aria-hidden="true"></i>&nbsp;{{ "archiveNoun" | i18n }}
</button>
</span>
@if (!(canArchive$ | async)) {
<app-premium-badge></app-premium-badge>
}
</li>
<li
class="filter-option"
*ngIf="!hideTrash"
[ngClass]="{ active: activeFilter.status === 'trash' }"
>
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyFilter('trash')"
[attr.aria-pressed]="activeFilter.status === 'trash'"
>
<i class="bwi bwi-fw bwi-trash" aria-hidden="true"></i>&nbsp;{{ "trash" | i18n }}
</button>
</span>
</li>
</ul>
</ng-container>

View File

@@ -0,0 +1,98 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { StatusFilterComponent } from "./status-filter.component";
describe("StatusFilterComponent", () => {
let component: StatusFilterComponent;
let fixture: ComponentFixture<StatusFilterComponent>;
let cipherArchiveService: jest.Mocked<CipherArchiveService>;
let accountService: FakeAccountService;
const mockUserId = Utils.newGuid() as UserId;
const event = new Event("click");
beforeEach(async () => {
accountService = mockAccountServiceWith(mockUserId);
cipherArchiveService = mock<CipherArchiveService>();
await TestBed.configureTestingModule({
declarations: [StatusFilterComponent],
providers: [
{ provide: AccountService, useValue: accountService },
{ provide: CipherArchiveService, useValue: cipherArchiveService },
{ provide: PremiumUpgradePromptService, useValue: mock<PremiumUpgradePromptService>() },
{
provide: BillingAccountProfileStateService,
useValue: mock<BillingAccountProfileStateService>(),
},
{ provide: I18nService, useValue: { t: (key: string) => key } },
],
imports: [JslibModule, PremiumBadgeComponent],
}).compileComponents();
fixture = TestBed.createComponent(StatusFilterComponent);
component = fixture.componentInstance;
component.activeFilter = new VaultFilter();
fixture.detectChanges();
});
describe("handleArchiveFilter", () => {
const applyFilter = jest.fn();
let promptForPremiumSpy: jest.SpyInstance;
beforeEach(() => {
applyFilter.mockClear();
component["applyFilter"] = applyFilter;
promptForPremiumSpy = jest.spyOn(component["premiumBadgeComponent"]()!, "promptForPremium");
});
it("should apply archive filter when userCanArchive returns true", async () => {
cipherArchiveService.userCanArchive$.mockReturnValue(of(true));
cipherArchiveService.archivedCiphers$.mockReturnValue(of([]));
await component["handleArchiveFilter"](event);
expect(applyFilter).toHaveBeenCalledWith("archive");
expect(promptForPremiumSpy).not.toHaveBeenCalled();
});
it("should apply archive filter when userCanArchive returns false but hasArchivedCiphers is true", async () => {
const mockCipher = new CipherView();
mockCipher.id = "test-id";
cipherArchiveService.userCanArchive$.mockReturnValue(of(false));
cipherArchiveService.archivedCiphers$.mockReturnValue(of([mockCipher]));
await component["handleArchiveFilter"](event);
expect(applyFilter).toHaveBeenCalledWith("archive");
expect(promptForPremiumSpy).not.toHaveBeenCalled();
});
it("should prompt for premium when userCanArchive returns false and hasArchivedCiphers is false", async () => {
cipherArchiveService.userCanArchive$.mockReturnValue(of(false));
cipherArchiveService.archivedCiphers$.mockReturnValue(of([]));
await component["handleArchiveFilter"](event);
expect(applyFilter).not.toHaveBeenCalled();
expect(promptForPremiumSpy).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,51 @@
import { Component, viewChild } from "@angular/core";
import { combineLatest, firstValueFrom, map, switchMap } from "rxjs";
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/status-filter.component";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-status-filter",
templateUrl: "status-filter.component.html",
standalone: false,
})
export class StatusFilterComponent extends BaseStatusFilterComponent {
private readonly premiumBadgeComponent = viewChild(PremiumBadgeComponent);
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
protected canArchive$ = this.userId$.pipe(
switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)),
);
protected hasArchivedCiphers$ = this.userId$.pipe(
switchMap((userId) =>
this.cipherArchiveService.archivedCiphers$(userId).pipe(map((ciphers) => ciphers.length > 0)),
),
);
constructor(
private accountService: AccountService,
private cipherArchiveService: CipherArchiveService,
) {
super();
}
protected async handleArchiveFilter(event: Event) {
const [canArchive, hasArchivedCiphers] = await firstValueFrom(
combineLatest([this.canArchive$, this.hasArchivedCiphers$]),
);
if (canArchive || hasArchivedCiphers) {
this.applyFilter("archive");
} else if (this.premiumBadgeComponent()) {
// The `premiumBadgeComponent` should always be defined here, adding the
// if to satisfy TypeScript.
await this.premiumBadgeComponent().promptForPremium(event);
}
}
}

View File

@@ -0,0 +1,39 @@
<div class="filter-heading">
<h2>
<button
type="button"
class="no-btn"
(click)="toggleCollapse()"
[attr.aria-expanded]="!isCollapsed"
aria-controls="type-filters"
>
<i
class="bwi bwi-fw"
aria-hidden="true"
[ngClass]="{
'bwi-angle-right': isCollapsed,
'bwi-angle-down': !isCollapsed,
}"
></i>
&nbsp;{{ typesNode.name | i18n }}
</button>
</h2>
</div>
<ul id="type-filters" *ngIf="!isCollapsed" class="filter-options">
@for (typeFilter of typeFilters$ | async; track typeFilter) {
<li class="filter-option" [ngClass]="{ active: activeFilter.cipherType === typeFilter.type }">
<span class="filter-buttons">
<button
type="button"
class="filter-button"
(click)="applyFilter(typeFilter.type)"
[attr.aria-pressed]="activeFilter.cipherType === typeFilter.type"
>
<i class="bwi bwi-fw {{ typeFilter.icon }}" aria-hidden="true"></i>&nbsp;{{
typeFilter.labelKey | i18n
}}
</button>
</span>
</li>
}
</ul>

View File

@@ -0,0 +1,34 @@
import { Component } from "@angular/core";
import { map, shareReplay } from "rxjs";
import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/type-filter.component";
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-type-filter",
templateUrl: "type-filter.component.html",
standalone: false,
})
export class TypeFilterComponent extends BaseTypeFilterComponent {
protected typeFilters$ = this.restrictedItemTypesService.restricted$.pipe(
map((restrictedItemTypes) =>
// Filter out restricted item types from the typeFilters array
CIPHER_MENU_ITEMS.filter(
(typeFilter) =>
!restrictedItemTypes.some(
(restrictedType) =>
restrictedType.allowViewOrgIds.length === 0 &&
restrictedType.cipherType === typeFilter.type,
),
),
),
shareReplay({ bufferSize: 1, refCount: true }),
);
constructor(private restrictedItemTypesService: RestrictedItemTypesService) {
super();
}
}

View File

@@ -0,0 +1,51 @@
<div class="container loading-spinner" *ngIf="!isLoaded">
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
</div>
<ng-container *ngIf="isLoaded">
<app-organization-filter
class="filter"
[hide]="hideOrganizations"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[organizations]="organizations"
[activeOrganizationDataOwnership]="activeOrganizationDataOwnershipPolicy"
[activeSingleOrganizationPolicy]="activeSingleOrganizationPolicy"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-organization-filter>
<app-status-filter
class="filter"
[hideFavorites]="hideFavorites"
[hideTrash]="hideTrash"
[hideArchive]="!showArchiveVaultFilter"
[activeFilter]="activeFilter"
(onFilterChange)="applyFilter($event)"
></app-status-filter>
<app-type-filter
class="filter"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-type-filter>
<app-folder-filter
class="filter"
[hide]="hideFolders"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[folderNodes]="folders$ | async"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
(onAddFolder)="addFolder()"
(onEditFolder)="editFolder($event)"
></app-folder-filter>
<app-collection-filter
class="filter"
[hide]="hideCollections"
[activeFilter]="activeFilter"
[collapsedFilterNodes]="collapsedFilterNodes"
[collectionNodes]="collections"
(onNodeCollapseStateChange)="toggleFilterNodeCollapseState($event)"
(onFilterChange)="applyFilter($event)"
></app-collection-filter>
</ng-container>

View File

@@ -0,0 +1,12 @@
import { Component } from "@angular/core";
import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/vault-filter.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-vault-filter",
templateUrl: "vault-filter.component.html",
standalone: false,
})
export class VaultFilterComponent extends BaseVaultFilterComponent {}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/vault/abstractions/deprecated-vault-filter.service";
import { VaultFilterService } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service";
import { CollectionFilterComponent } from "./filters/collection-filter.component";
import { FolderFilterComponent } from "./filters/folder-filter.component";
import { OrganizationFilterComponent } from "./filters/organization-filter.component";
import { StatusFilterComponent } from "./filters/status-filter.component";
import { TypeFilterComponent } from "./filters/type-filter.component";
import { VaultFilterComponent } from "./vault-filter.component";
@NgModule({
imports: [CommonModule, JslibModule, PremiumBadgeComponent],
declarations: [
VaultFilterComponent,
CollectionFilterComponent,
FolderFilterComponent,
OrganizationFilterComponent,
StatusFilterComponent,
TypeFilterComponent,
],
exports: [VaultFilterComponent],
providers: [
{
provide: DeprecatedVaultFilterServiceAbstraction,
useClass: VaultFilterService,
},
],
})
export class VaultFilterModule {}