1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00
Files
browser/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts
Daniel James Smith 9ddaf96020 [PM-13811] Remove conditional code for extension refresh on web (#13145)
* Enable UI refresh on web by default

Removing all conditional code around the `ExtensionRefresh`-feature-flag on the web-UI

* Remove no longer needed extensRefresh helpers

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
2025-02-10 14:20:05 +01:00

251 lines
7.8 KiB
TypeScript

// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import {
CollectionAdminService,
CollectionAdminView,
Unassigned,
} from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import {
BreadcrumbsModule,
DialogService,
MenuModule,
SearchModule,
SimpleDialogOptions,
} from "@bitwarden/components";
import { HeaderModule } from "../../../layouts/header/header.module";
import { SharedModule } from "../../../shared";
import { CollectionDialogTabType } from "../../components/collection-dialog";
import {
All,
RoutedVaultFilterModel,
} from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model";
@Component({
standalone: true,
selector: "app-org-vault-header",
templateUrl: "./vault-header.component.html",
imports: [
CommonModule,
MenuModule,
SharedModule,
BreadcrumbsModule,
HeaderModule,
SearchModule,
JslibModule,
],
})
export class VaultHeaderComponent implements OnInit {
protected All = All;
protected Unassigned = Unassigned;
/**
* Boolean to determine the loading state of the header.
* Shows a loading spinner if set to true
*/
@Input() loading: boolean;
/** Current active filter */
@Input() filter: RoutedVaultFilterModel;
/** The organization currently being viewed */
@Input() organization: Organization;
/** Currently selected collection */
@Input() collection?: TreeNode<CollectionAdminView>;
/** The current search text in the header */
@Input() searchText: string;
/** Emits an event when the new item button is clicked in the header */
@Output() onAddCipher = new EventEmitter<CipherType | undefined>();
/** Emits an event when the new collection button is clicked in the header */
@Output() onAddCollection = new EventEmitter<void>();
/** Emits an event when the edit collection button is clicked in the header */
@Output() onEditCollection = new EventEmitter<{
tab: CollectionDialogTabType;
readonly: boolean;
}>();
/** Emits an event when the delete collection button is clicked in the header */
@Output() onDeleteCollection = new EventEmitter<void>();
/** Emits an event when the search text changes in the header*/
@Output() searchTextChanged = new EventEmitter<string>();
protected CollectionDialogTabType = CollectionDialogTabType;
/** The cipher type enum. */
protected CipherType = CipherType;
constructor(
private i18nService: I18nService,
private dialogService: DialogService,
private collectionAdminService: CollectionAdminService,
private router: Router,
private configService: ConfigService,
) {}
async ngOnInit() {}
get title() {
const headerType = this.i18nService.t("collections").toLowerCase();
if (this.collection != null) {
return this.collection.node.name;
}
if (this.filter.collectionId === Unassigned) {
return this.i18nService.t("unassigned");
}
return this.organization?.name
? `${this.organization?.name} ${headerType}`
: this.i18nService.t("collections");
}
get icon() {
return this.filter.collectionId !== undefined ? "bwi-collection" : "";
}
protected get showBreadcrumbs() {
return this.filter.collectionId !== undefined && this.filter.collectionId !== All;
}
/**
* A list of collection filters that form a chain from the organization root to currently selected collection.
* Begins from the organization root and excludes the currently selected collection.
*/
protected get collections() {
if (this.collection == undefined) {
return [];
}
const collections = [this.collection];
while (collections[collections.length - 1].parent != undefined) {
collections.push(collections[collections.length - 1].parent);
}
return collections
.slice(1)
.reverse()
.map((treeNode) => treeNode.node);
}
private showFreeOrgUpgradeDialog(): void {
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canEditSubscription
? "freeOrgMaxCollectionReachedManageBilling"
: "freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections,
),
type: "primary",
};
if (this.organization.canEditSubscription) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
orgUpgradeSimpleDialogOpts.cancelButtonText = null; // hide secondary btn
}
const simpleDialog = this.dialogService.openSimpleDialogRef(orgUpgradeSimpleDialogOpts);
// 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
firstValueFrom(simpleDialog.closed).then((result: boolean | undefined) => {
if (!result) {
return;
}
if (result && this.organization.canEditSubscription) {
// 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
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});
}
});
}
get canEditCollection(): boolean {
// Only edit collections if not editing "Unassigned"
if (this.collection === undefined) {
return false;
}
// Otherwise, check if we can edit the specified collection
return this.collection.node.canEdit(this.organization);
}
addCipher(cipherType?: CipherType) {
this.onAddCipher.emit(cipherType);
}
async addCollection() {
if (this.organization.productTierType === ProductTierType.Free) {
const collections = await this.collectionAdminService.getAll(this.organization.id);
if (collections.length === this.organization.maxCollections) {
this.showFreeOrgUpgradeDialog();
return;
}
}
this.onAddCollection.emit();
}
async editCollection(tab: CollectionDialogTabType, readonly: boolean): Promise<void> {
this.onEditCollection.emit({ tab, readonly });
}
get canDeleteCollection(): boolean {
// Only delete collections if not deleting "Unassigned"
if (this.collection === undefined) {
return false;
}
// Otherwise, check if we can delete the specified collection
return this.collection.node.canDelete(this.organization);
}
get canViewCollectionInfo(): boolean {
return this.collection.node.canViewCollectionInfo(this.organization);
}
get canCreateCollection(): boolean {
return this.organization?.canCreateNewCollections;
}
get canCreateCipher(): boolean {
if (this.organization?.isProviderUser && !this.organization?.isMember) {
return false;
}
return true;
}
deleteCollection() {
this.onDeleteCollection.emit();
}
onSearchTextChanged(t: string) {
this.searchText = t;
this.searchTextChanged.emit(t);
}
}