@ -1,25 +1,31 @@
@@ -1,25 +1,31 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import {
Dialog as CdkDialog ,
DialogConfig as CdkDialogConfig ,
DialogRef as CdkDialogRefBase ,
DIALOG_DATA ,
DialogCloseOptions ,
DEFAULT_DIALOG_CONFIG ,
Dialog ,
DialogConfig ,
DialogRef ,
DIALOG_SCROLL_STRATEGY ,
} from "@angular/cdk/dialog" ;
import { ComponentType , ScrollStrategy } from "@angular/cdk/overlay" ;
import { ComponentPortal , Portal } from "@angular/cdk/portal" ;
import { Injectable , Injector , TemplateRef , inject } from "@angular/core" ;
import { takeUntilDestroyed } from "@angular/core/rxjs-interop" ;
import { ComponentType , Overlay , OverlayContainer , ScrollStrategy } from "@angular/cdk/overlay" ;
import {
Inject ,
Injectable ,
Injector ,
OnDestroy ,
Optional ,
SkipSelf ,
TemplateRef ,
} from "@angular/core" ;
import { NavigationEnd , Router } from "@angular/router" ;
import { filter , firstValueFrom , map , Observable , Subject , switchMap } from "rxjs" ;
import { filter , firstValueFrom , Subject , switchMap , takeUntil } from "rxjs" ;
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service" ;
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status" ;
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service" ;
import { DrawerService } from "../drawer/drawer.service" ;
import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component" ;
import { SimpleDialogOptions } from "./simple-dialog/types" ;
import { SimpleDialogOptions , Translation } from "./simple-dialog/types" ;
/ * *
* The default ` BlockScrollStrategy ` does not work well with virtual scrolling .
@ -42,163 +48,61 @@ class CustomBlockScrollStrategy implements ScrollStrategy {
@@ -42,163 +48,61 @@ class CustomBlockScrollStrategy implements ScrollStrategy {
detach() { }
}
export abstract class DialogRef < R = unknown , C = unknown >
implements Pick < CdkDialogRef < R , C > , "close" | "closed" | "disableClose" | "componentInstance" >
{
abstract readonly isDrawer? : boolean ;
// --- From CdkDialogRef ---
abstract close ( result? : R , options? : DialogCloseOptions ) : void ;
abstract readonly closed : Observable < R | undefined > ;
abstract disableClose : boolean | undefined ;
/ * *
* @deprecated
* Does not work with drawer dialogs .
* * /
abstract componentInstance : C | null ;
}
export type DialogConfig < D = unknown , R = unknown > = Pick <
CdkDialogConfig < D , R > ,
"data" | "disableClose" | "ariaModal" | "positionStrategy" | "height" | "width"
> ;
class DrawerDialogRef < R = unknown , C = unknown > implements DialogRef < R , C > {
readonly isDrawer = true ;
private _closed = new Subject < R | undefined > ( ) ;
closed = this . _closed . asObservable ( ) ;
disableClose = false ;
/** The portal containing the drawer */
portal? : Portal < unknown > ;
constructor ( private drawerService : DrawerService ) { }
close ( result? : R , _options? : DialogCloseOptions ) : void {
if ( this . disableClose ) {
return ;
}
this . drawerService . close ( this . portal ! ) ;
this . _closed . next ( result ) ;
this . _closed . complete ( ) ;
}
componentInstance : C | null = null ;
}
/ * *
* DialogRef that delegates functionality to the CDK implementation
* * /
export class CdkDialogRef < R = unknown , C = unknown > implements DialogRef < R , C > {
readonly isDrawer = false ;
/** This is not available until after construction, @see DialogService.open. */
cdkDialogRefBase ! : CdkDialogRefBase < R , C > ;
// --- Delegated to CdkDialogRefBase ---
close ( result? : R , options? : DialogCloseOptions ) : void {
this . cdkDialogRefBase . close ( result , options ) ;
}
@Injectable ( )
export class DialogService extends Dialog implements OnDestroy {
private _destroy $ = new Subject < void > ( ) ;
get closed ( ) : Observable < R | undefined > {
return this . cdkDialogRefBase . closed ;
}
private backDropClasses = [ "tw-fixed" , "tw-bg-black" , "tw-bg-opacity-30" , "tw-inset-0" ] ;
get disableClose ( ) : boolean | undefined {
return this . cdkDialogRefBase . disableClose ;
}
set disableClose ( value : boolean | undefined ) {
this . cdkDialogRefBase . disableClose = value ;
}
private defaultScrollStrategy = new CustomBlockScrollStrategy ( ) ;
// Delegate the `componentInstance` property to the CDK DialogRef
get componentInstance ( ) : C | null {
return this . cdkDialogRefBase . componentInstance ;
}
}
constructor (
/** Parent class constructor */
_overlay : Overlay ,
_injector : Injector ,
@Optional ( ) @Inject ( DEFAULT_DIALOG_CONFIG ) _defaultOptions : DialogConfig ,
@Optional ( ) @SkipSelf ( ) _parentDialog : Dialog ,
_overlayContainer : OverlayContainer ,
@Inject ( DIALOG_SCROLL_STRATEGY ) scrollStrategy : any ,
@Injectable ( )
export class DialogService {
private dialog = inject ( CdkDialog ) ;
private drawerService = inject ( DrawerService ) ;
private injector = inject ( Injector ) ;
private router = inject ( Router , { optional : true } ) ;
private authService = inject ( AuthService , { optional : true } ) ;
private i18nService = inject ( I18nService ) ;
/** Not in parent class */
@Optional ( ) router : Router ,
@Optional ( ) authService : AuthService ,
private backDropClasses = [ "tw-fixed" , "tw-bg-black" , "tw-bg-opacity-30" , "tw-inset-0" ] ;
private defaultScrollStrategy = new CustomBlockScrollStrategy ( ) ;
private activeDrawer : DrawerDialogRef < any , any > | null = null ;
protected i18nService : I18nService ,
) {
super ( _overlay , _injector , _defaultOptions , _parentDialog , _overlayContainer , scrollStrategy ) ;
constructor ( ) {
/ * *
* TODO : This logic should exist outside of ` libs/components ` .
* @see https : //bitwarden.atlassian.net/browse/CL-657
* * /
/** Close all open dialogs if the vault locks */
if ( this . router && this . authService ) {
this . router . events
if ( router && authService ) {
router . events
. pipe (
filter ( ( event ) = > event instanceof NavigationEnd ) ,
switchMap ( ( ) = > this . authService ! . getAuthStatus ( ) ) ,
switchMap ( ( ) = > authService . getAuthStatus ( ) ) ,
filter ( ( v ) = > v !== AuthenticationStatus . Unlocked ) ,
takeUntilDestroyed ( ) ,
takeUntil ( this . _destroy $ ) ,
)
. subscribe ( ( ) = > this . closeAll ( ) ) ;
}
}
open < R = unknown , D = unknown , C = unknown > (
override ngOnDestroy ( ) : void {
this . _destroy $ . next ( ) ;
this . _destroy $ . complete ( ) ;
super . ngOnDestroy ( ) ;
}
override open < R = unknown , D = unknown , C = unknown > (
componentOrTemplateRef : ComponentType < C > | TemplateRef < C > ,
config? : DialogConfig < D , DialogRef < R , C > > ,
) : DialogRef < R , C > {
/ * *
* This is a bit circular in nature :
* We need the DialogRef instance for the DI injector that is passed * to * ` Dialog.open ` ,
* but we get the base CDK DialogRef instance * from * ` Dialog.open ` .
*
* To break the circle , we define CDKDialogRef as a wrapper for the CDKDialogRefBase .
* This allows us to create the class instance and provide the base instance later , almost like "deferred inheritance" .
* * /
const ref = new CdkDialogRef < R , C > ( ) ;
const injector = this . createInjector ( {
data : config?.data ,
dialogRef : ref ,
} ) ;
// Merge the custom config with the default config
const _config = {
config = {
backdropClass : this.backDropClasses ,
scrollStrategy : this.defaultScrollStrategy ,
injector ,
. . . config ,
} ;
ref . cdkDialogRefBase = this . dialog . open < R , D , C > ( componentOrTemplateRef , _config ) ;
return ref ;
}
/** Opens a dialog in the side drawer */
openDrawer < R = unknown , D = unknown , C = unknown > (
component : ComponentType < C > ,
config? : DialogConfig < D , DialogRef < R , C > > ,
) : DialogRef < R , C > {
this . activeDrawer ? . close ( ) ;
/ * *
* This is also circular . When creating the DrawerDialogRef , we do not yet have a portal instance to provide to the injector .
* Similar to ` this.open ` , we get around this with mutability .
* /
this . activeDrawer = new DrawerDialogRef ( this . drawerService ) ;
const portal = new ComponentPortal (
component ,
null ,
this . createInjector ( { data : config?.data , dialogRef : this.activeDrawer } ) ,
) ;
this . activeDrawer . portal = portal ;
this . drawerService . open ( portal ) ;
return this . activeDrawer ;
return super . open ( componentOrTemplateRef , config ) ;
}
/ * *
@ -209,7 +113,8 @@ export class DialogService {
@@ -209,7 +113,8 @@ export class DialogService {
* /
async openSimpleDialog ( simpleDialogOptions : SimpleDialogOptions ) : Promise < boolean > {
const dialogRef = this . openSimpleDialogRef ( simpleDialogOptions ) ;
return firstValueFrom ( dialogRef . closed . pipe ( map ( ( v : boolean | undefined ) = > ! ! v ) ) ) ;
return firstValueFrom ( dialogRef . closed ) ;
}
/ * *
@ -229,29 +134,20 @@ export class DialogService {
@@ -229,29 +134,20 @@ export class DialogService {
} ) ;
}
/** Close all open dialogs */
closeAll ( ) : void {
return this . dialog . closeAll ( ) ;
}
protected translate ( translation : string | Translation , defaultKey? : string ) : string {
if ( translation == null && defaultKey == null ) {
return null ;
}
/** The injector that is passed to the opened dialog */
private createInjector ( opts : { data : unknown ; dialogRef : DialogRef } ) : Injector {
return Injector . create ( {
providers : [
{
provide : DIALOG_DATA ,
useValue : opts.data ,
} ,
{
provide : DialogRef ,
useValue : opts.dialogRef ,
} ,
{
provide : CdkDialogRefBase ,
useValue : opts.dialogRef ,
} ,
] ,
parent : this.injector ,
} ) ;
if ( translation == null ) {
return this . i18nService . t ( defaultKey ) ;
}
// Translation interface use implies we must localize.
if ( typeof translation === "object" ) {
return this . i18nService . t ( translation . key , . . . ( translation . placeholders ? ? [ ] ) ) ;
}
return translation ;
}
}