mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-1504] Migrate Dialogs to DialogService (#5013)
This PR introduces a generic `DialogService` which can be used by all the clients. This allows us to decouple dialogs from the `PlatformUtilsHelper`. The `DialogService` provides a new method, `openSimpleDialog` which is the new interface for that type of dialogs. This gives us 3 different implementations: - Web: DialogService modern dialogs - Browser: SweetAlert - Desktop: Native electron based
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { DialogModule as CdkDialogModule } from "@angular/cdk/dialog";
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
|
||||
import { ButtonModule } from "../button";
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { SharedModule } from "../shared";
|
||||
@@ -29,6 +31,11 @@ import { IconDirective, SimpleDialogComponent } from "./simple-dialog/simple-dia
|
||||
DialogCloseDirective,
|
||||
IconDirective,
|
||||
],
|
||||
providers: [DialogService],
|
||||
providers: [
|
||||
{
|
||||
provide: DialogServiceAbstraction,
|
||||
useClass: DialogService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class DialogModule {}
|
||||
|
||||
@@ -16,26 +16,24 @@ import {
|
||||
TemplateRef,
|
||||
} from "@angular/core";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import { filter, Subject, switchMap, takeUntil } from "rxjs";
|
||||
import { filter, firstValueFrom, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import {
|
||||
DialogServiceAbstraction,
|
||||
SimpleDialogCloseType,
|
||||
} from "@bitwarden/angular/services/dialog";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
|
||||
import { SimpleDialogOptions } from "./simple-configurable-dialog/models/simple-dialog-options";
|
||||
import { SimpleDialogOptions } from "../../../angular/src/services/dialog/simple-dialog-options";
|
||||
|
||||
import { SimpleConfigurableDialogComponent } from "./simple-configurable-dialog/simple-configurable-dialog.component";
|
||||
|
||||
@Injectable()
|
||||
export class DialogService extends Dialog implements OnDestroy {
|
||||
export class DialogService extends Dialog implements OnDestroy, DialogServiceAbstraction {
|
||||
private _destroy$ = new Subject<void>();
|
||||
|
||||
private backDropClasses = [
|
||||
"tw-fixed",
|
||||
"tw-bg-black",
|
||||
"tw-bg-opacity-30",
|
||||
"tw-inset-0",
|
||||
// CDK dialog panels have a default z-index of 1000. Matching this allows us to easily stack dialogs.
|
||||
"tw-z-[1000]",
|
||||
];
|
||||
private backDropClasses = ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0"];
|
||||
|
||||
constructor(
|
||||
/** Parent class constructor */
|
||||
@@ -83,16 +81,33 @@ export class DialogService extends Dialog implements OnDestroy {
|
||||
return super.open(componentOrTemplateRef, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a simple dialog, returns true if the user accepted the dialog.
|
||||
*
|
||||
* @param {SimpleDialogOptions} simpleDialogOptions - An object containing options for the dialog.
|
||||
* @returns `boolean` - True if the user accepted the dialog, false otherwise.
|
||||
*/
|
||||
async openSimpleDialog(simpleDialogOptions: SimpleDialogOptions): Promise<boolean> {
|
||||
const dialogRef = this.open(SimpleConfigurableDialogComponent, {
|
||||
data: simpleDialogOptions,
|
||||
disableClose: simpleDialogOptions.disableClose,
|
||||
});
|
||||
|
||||
return (await firstValueFrom(dialogRef.closed)) == SimpleDialogCloseType.ACCEPT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a simple dialog.
|
||||
*
|
||||
* @deprecated Use `openSimpleDialog` instead. If you find a use case for the `dialogRef`
|
||||
* please let #wg-component-library know and we can un-deprecate this method.
|
||||
*
|
||||
* @param {SimpleDialogOptions} simpleDialogOptions - An object containing options for the dialog.
|
||||
* @returns `DialogRef` - The reference to the opened dialog.
|
||||
* Contains a closed observable which can be subscribed to for determining which button
|
||||
* a user pressed (see `SimpleDialogCloseType`)
|
||||
*/
|
||||
openSimpleDialog(simpleDialogOptions: SimpleDialogOptions): DialogRef {
|
||||
// Method needs to return dialog reference so devs can sub to closed and get results.
|
||||
openSimpleDialogRef(simpleDialogOptions: SimpleDialogOptions): DialogRef {
|
||||
return this.open(SimpleConfigurableDialogComponent, {
|
||||
data: simpleDialogOptions,
|
||||
disableClose: simpleDialogOptions.disableClose,
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
export * from "./dialog.module";
|
||||
export * from "./dialog.service";
|
||||
export * from "./simple-configurable-dialog/models/simple-dialog-options";
|
||||
export * from "./simple-configurable-dialog/models/simple-dialog-type.enum";
|
||||
export * from "./simple-configurable-dialog/models/simple-dialog-close-type.enum";
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export enum SimpleDialogCloseType {
|
||||
ACCEPT = "accept",
|
||||
CANCEL = "cancel",
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { SimpleDialogType } from "./simple-dialog-type.enum";
|
||||
import { Translation } from "./translation";
|
||||
|
||||
// Using type lets devs skip optional params w/out having to pass undefined.
|
||||
/**
|
||||
*
|
||||
* @typedef {Object} SimpleDialogOptions - A configuration type for the Simple Dialog component
|
||||
*/
|
||||
export type SimpleDialogOptions = {
|
||||
/**
|
||||
* Dialog title.
|
||||
*
|
||||
* If not localized, pass in a `Translation`. */
|
||||
title: string | Translation;
|
||||
|
||||
/** Dialog content.
|
||||
*
|
||||
* If not localized, pass in a `Translation`. */
|
||||
content: string | Translation;
|
||||
|
||||
/** Dialog type. It controls default icons and icon colors. */
|
||||
type: SimpleDialogType;
|
||||
|
||||
/** Dialog custom icon class.
|
||||
*
|
||||
* If not provided, a standard icon will be inferred from type.
|
||||
* Note: icon color is enforced based on dialog type. */
|
||||
icon?: string;
|
||||
|
||||
/** Dialog custom accept button text.
|
||||
*
|
||||
* If not provided, ("yes" | i18n) will be used.
|
||||
*
|
||||
* If not localized, pass in a `Translation` */
|
||||
acceptButtonText?: string | Translation;
|
||||
|
||||
/**
|
||||
* Dialog custom cancel button text.
|
||||
*
|
||||
* If not provided, ("no" | i18n) will be used.
|
||||
*
|
||||
* If custom acceptButtonText is passed in, ("cancel" | i18n) will be used.
|
||||
*
|
||||
* If null is provided, the cancel button will be removed.
|
||||
*
|
||||
* If not localized, pass in a `Translation` */
|
||||
cancelButtonText?: string | Translation;
|
||||
|
||||
/** Whether or not the user can use escape or clicking the backdrop to close the dialog */
|
||||
disableClose?: boolean;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export enum SimpleDialogType {
|
||||
PRIMARY = "primary",
|
||||
SUCCESS = "success",
|
||||
INFO = "info",
|
||||
WARNING = "warning",
|
||||
DANGER = "danger",
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface Translation {
|
||||
key: string;
|
||||
placeholders?: Array<string | number>;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
import {
|
||||
SimpleDialogType,
|
||||
SimpleDialogCloseType,
|
||||
Translation,
|
||||
} from "@bitwarden/angular/services/dialog";
|
||||
import { SimpleDialogOptions } from "@bitwarden/angular/services/dialog/simple-dialog-options";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
|
||||
import { SimpleDialogCloseType } from "./models/simple-dialog-close-type.enum";
|
||||
import { SimpleDialogOptions } from "./models/simple-dialog-options";
|
||||
import { SimpleDialogType } from "./models/simple-dialog-type.enum";
|
||||
import { Translation } from "./models/translation";
|
||||
|
||||
const DEFAULT_ICON: Record<SimpleDialogType, string> = {
|
||||
[SimpleDialogType.PRIMARY]: "bwi-business",
|
||||
[SimpleDialogType.SUCCESS]: "bwi-star",
|
||||
@@ -70,7 +71,7 @@ export class SimpleConfigurableDialogComponent {
|
||||
private translate(translation: string | Translation, defaultKey?: string): string {
|
||||
// Translation interface use implies we must localize.
|
||||
if (typeof translation === "object") {
|
||||
return this.i18nService.t(translation.key, ...translation.placeholders);
|
||||
return this.i18nService.t(translation.key, ...(translation.placeholders ?? []));
|
||||
}
|
||||
|
||||
// Use string that is already translated or use default key post translate
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DialogModule, DialogRef } from "@angular/cdk/dialog";
|
||||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { Component } from "@angular/core";
|
||||
import { Meta, moduleMetadata, Story } from "@storybook/angular";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { SimpleDialogType, SimpleDialogOptions } from "@bitwarden/angular/services/dialog";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
|
||||
import { ButtonModule } from "../../button";
|
||||
@@ -15,10 +15,6 @@ import { DialogCloseDirective } from "../directives/dialog-close.directive";
|
||||
import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive";
|
||||
import { SimpleDialogComponent } from "../simple-dialog/simple-dialog.component";
|
||||
|
||||
import { SimpleDialogCloseType } from "./models/simple-dialog-close-type.enum";
|
||||
import { SimpleDialogOptions } from "./models/simple-dialog-options";
|
||||
import { SimpleDialogType } from "./models/simple-dialog-type.enum";
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2 class="tw-text-main">Dialog Type Examples:</h2>
|
||||
@@ -114,8 +110,7 @@ import { SimpleDialogType } from "./models/simple-dialog-type.enum";
|
||||
</div>
|
||||
|
||||
<bit-callout *ngIf="showCallout" [type]="calloutType" title="Dialog Close Result">
|
||||
<span *ngIf="dialogCloseResult">{{ dialogCloseResult }}</span>
|
||||
<span *ngIf="!dialogCloseResult">undefined</span>
|
||||
{{ dialogCloseResult }}
|
||||
</bit-callout>
|
||||
`,
|
||||
})
|
||||
@@ -189,22 +184,19 @@ class StoryDialogComponent {
|
||||
|
||||
showCallout = false;
|
||||
calloutType = "info";
|
||||
dialogCloseResult: undefined | SimpleDialogCloseType;
|
||||
dialogCloseResult: boolean;
|
||||
|
||||
constructor(public dialogService: DialogService, private i18nService: I18nService) {}
|
||||
|
||||
openSimpleConfigurableDialog(opts: SimpleDialogOptions) {
|
||||
const dialogReference: DialogRef = this.dialogService.openSimpleDialog(opts);
|
||||
async openSimpleConfigurableDialog(opts: SimpleDialogOptions) {
|
||||
this.dialogCloseResult = await this.dialogService.openSimpleDialog(opts);
|
||||
|
||||
firstValueFrom(dialogReference.closed).then((result: SimpleDialogCloseType | undefined) => {
|
||||
this.showCallout = true;
|
||||
this.dialogCloseResult = result;
|
||||
if (result && result === SimpleDialogCloseType.ACCEPT) {
|
||||
this.calloutType = "success";
|
||||
} else {
|
||||
this.calloutType = "info";
|
||||
}
|
||||
});
|
||||
this.showCallout = true;
|
||||
if (this.dialogCloseResult) {
|
||||
this.calloutType = "success";
|
||||
} else {
|
||||
this.calloutType = "info";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user