// FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, NgZone, OnInit, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { lastValueFrom, Observable, switchMap, EMPTY } from "rxjs"; import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; import { NoSendsIcon } from "@bitwarden/assets/svg"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { SendFilterType } from "@bitwarden/common/tools/send/types/send-filter-type"; import { SendType } from "@bitwarden/common/tools/send/types/send-type"; import { SendId } from "@bitwarden/common/types/guid"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { DialogRef, DialogService, NoItemsModule, SearchModule, TableDataSource, ToastService, ToggleGroupModule, } from "@bitwarden/components"; import { DefaultSendFormConfigService, SendFormConfig, SendAddEditDialogComponent, SendItemDialogResult, SendTableComponent, } from "@bitwarden/send-ui"; import { HeaderModule } from "../../layouts/header/header.module"; import { SharedModule } from "../../shared"; import { NewSendDropdownComponent } from "./new-send/new-send-dropdown.component"; import { SendSuccessDrawerDialogComponent } from "./shared"; const BroadcasterSubscriptionId = "SendComponent"; // 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-send", imports: [ SharedModule, SearchModule, NoItemsModule, HeaderModule, NewSendDropdownComponent, ToggleGroupModule, SendTableComponent, ], templateUrl: "send.component.html", providers: [DefaultSendFormConfigService], }) export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy { private sendItemDialogRef?: DialogRef | undefined; noItemIcon = NoSendsIcon; selectedToggleValue?: SendFilterType; SendUIRefresh$: Observable; override set filteredSends(filteredSends: SendView[]) { super.filteredSends = filteredSends; this.dataSource.data = filteredSends; } override get filteredSends() { return super.filteredSends; } protected dataSource = new TableDataSource(); constructor( sendService: SendService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone, searchService: SearchService, policyService: PolicyService, private broadcasterService: BroadcasterService, logService: LogService, sendApiService: SendApiService, dialogService: DialogService, toastService: ToastService, private addEditFormConfigService: DefaultSendFormConfigService, accountService: AccountService, private route: ActivatedRoute, private router: Router, private configService: ConfigService, ) { super( sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService, policyService, logService, sendApiService, dialogService, toastService, accountService, ); this.SendUIRefresh$ = this.configService.getFeatureFlag$(FeatureFlag.SendUIRefresh); this.SendUIRefresh$.pipe( switchMap((sendUiRefreshEnabled) => { if (sendUiRefreshEnabled) { return this.route.queryParamMap; } return EMPTY; }), takeUntilDestroyed(), ).subscribe((params) => { const typeParam = params.get("type"); const value = ( typeParam === SendFilterType.Text || typeParam === SendFilterType.File ? typeParam : SendFilterType.All ) as SendFilterType; this.selectedToggleValue = value; if (this.loaded) { this.applyTypeFilter(value); } }); } async ngOnInit() { await super.ngOnInit(); this.onSuccessfulLoad = async () => { this.applyTypeFilter(this.selectedToggleValue); }; await this.load(); // Broadcaster subscription - load if sync completes in the background this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { // 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.ngZone.run(async () => { switch (message.command) { case "syncCompleted": if (message.successfully) { await this.load(); } break; } }); }); } ngOnDestroy() { this.dialogService.closeAll(); this.dialogService.closeDrawer(); this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } async addSend() { if (this.disableSend) { return; } const config = await this.addEditFormConfigService.buildConfig("add", null, 0); await this.openSendItemDialog(config); } async editSend(send: SendView) { const config = await this.addEditFormConfigService.buildConfig( send == null ? "add" : "edit", send == null ? null : (send.id as SendId), send.type, ); await this.openSendItemDialog(config); } /** * Opens the send item dialog. * @param formConfig The form configuration. * */ async openSendItemDialog(formConfig: SendFormConfig) { const useRefresh = await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh); // Prevent multiple dialogs from being opened but allow drawers since they will prevent multiple being open themselves if (this.sendItemDialogRef && !useRefresh) { return; } if (useRefresh) { this.sendItemDialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig, }); } else { this.sendItemDialogRef = SendAddEditDialogComponent.open(this.dialogService, { formConfig, }); } const result: SendItemDialogResult = await lastValueFrom(this.sendItemDialogRef.closed); this.sendItemDialogRef = undefined; // If the dialog was closed by deleting the cipher, refresh the vault. if ( result?.result === SendItemDialogResult.Deleted || result?.result === SendItemDialogResult.Saved ) { await this.load(); } if ( result?.result === SendItemDialogResult.Saved && result?.send && (await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh)) ) { this.dialogService.openDrawer(SendSuccessDrawerDialogComponent, { data: result.send, }); } } private applyTypeFilter(value: SendFilterType) { if (value === SendFilterType.All) { this.selectAll(); } else if (value === SendFilterType.Text) { this.selectType(SendType.Text); } else if (value === SendFilterType.File) { this.selectType(SendType.File); } } onToggleChange(value: SendFilterType) { const queryParams = value === SendFilterType.All ? { type: null } : { type: value }; this.router .navigate([], { relativeTo: this.route, queryParams, queryParamsHandling: "merge", }) .catch((err) => { this.logService.error("Failed to update route query params:", err); }); } }