mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +01:00
[CL-18] toast component and service (#6490)
Update toast styles and new service to CL.
This commit is contained in:
parent
9277465951
commit
d5f503a0d6
@ -3000,6 +3000,9 @@
|
|||||||
"message": "Error saving credentials. Check console for details.",
|
"message": "Error saving credentials. Check console for details.",
|
||||||
"description": "Notification message for when saving credentials has failed."
|
"description": "Notification message for when saving credentials has failed."
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"removePasskey": {
|
"removePasskey": {
|
||||||
"message": "Remove passkey"
|
"message": "Remove passkey"
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { SecurityContext } from "@angular/core";
|
import { ToastService } from "@bitwarden/components";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { ToastrService } from "ngx-toastr";
|
|
||||||
|
|
||||||
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "./browser-platform-utils.service";
|
||||||
|
|
||||||
export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService {
|
export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService {
|
||||||
constructor(
|
constructor(
|
||||||
private sanitizer: DomSanitizer,
|
private toastService: ToastService,
|
||||||
private toastrService: ToastrService,
|
|
||||||
clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||||
biometricCallback: () => Promise<boolean>,
|
biometricCallback: () => Promise<boolean>,
|
||||||
win: Window & typeof globalThis,
|
win: Window & typeof globalThis,
|
||||||
@ -21,20 +18,6 @@ export class ForegroundPlatformUtilsService extends BrowserPlatformUtilsService
|
|||||||
text: string | string[],
|
text: string | string[],
|
||||||
options?: any,
|
options?: any,
|
||||||
): void {
|
): void {
|
||||||
if (typeof text === "string") {
|
this.toastService._showToast({ type, title, text, options });
|
||||||
// Already in the correct format
|
|
||||||
} else if (text.length === 1) {
|
|
||||||
text = text[0];
|
|
||||||
} else {
|
|
||||||
let message = "";
|
|
||||||
text.forEach(
|
|
||||||
(t: string) =>
|
|
||||||
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>"),
|
|
||||||
);
|
|
||||||
text = message;
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
this.toastrService.show(text, title, options, "toast-" + type);
|
|
||||||
// noop
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||||
import { ToastrService } from "ngx-toastr";
|
|
||||||
import { filter, concatMap, Subject, takeUntil, firstValueFrom, map } from "rxjs";
|
import { filter, concatMap, Subject, takeUntil, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { BrowserApi } from "../platform/browser/browser-api";
|
import { BrowserApi } from "../platform/browser/browser-api";
|
||||||
import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service";
|
import { ZonedMessageListenerService } from "../platform/browser/zoned-message-listener.service";
|
||||||
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
|
||||||
import { ForegroundPlatformUtilsService } from "../platform/services/platform-utils/foreground-platform-utils.service";
|
|
||||||
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
|
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
|
||||||
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
|
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
|
||||||
|
|
||||||
@ -35,7 +34,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private toastrService: ToastrService,
|
|
||||||
private broadcasterService: BroadcasterService,
|
private broadcasterService: BroadcasterService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
@ -46,9 +44,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private platformUtilsService: ForegroundPlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private browserMessagingApi: ZonedMessageListenerService,
|
private browserMessagingApi: ZonedMessageListenerService,
|
||||||
|
private toastService: ToastService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@ -83,10 +82,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
if (msg.command === "doneLoggingOut") {
|
if (msg.command === "doneLoggingOut") {
|
||||||
this.authService.logOut(async () => {
|
this.authService.logOut(async () => {
|
||||||
if (msg.expired) {
|
if (msg.expired) {
|
||||||
this.showToast({
|
this.toastService.showToast({
|
||||||
type: "warning",
|
variant: "warning",
|
||||||
title: this.i18nService.t("loggedOut"),
|
title: this.i18nService.t("loggedOut"),
|
||||||
text: this.i18nService.t("loginExpired"),
|
message: this.i18nService.t("loginExpired"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.showNativeMessagingFingerprintDialog(msg);
|
this.showNativeMessagingFingerprintDialog(msg);
|
||||||
} else if (msg.command === "showToast") {
|
} else if (msg.command === "showToast") {
|
||||||
this.showToast(msg);
|
this.toastService._showToast(msg);
|
||||||
} else if (msg.command === "reloadProcess") {
|
} else if (msg.command === "reloadProcess") {
|
||||||
const forceWindowReload =
|
const forceWindowReload =
|
||||||
this.platformUtilsService.isSafari() ||
|
this.platformUtilsService.isSafari() ||
|
||||||
|
@ -11,11 +11,10 @@ import { BrowserModule } from "@angular/platform-browser";
|
|||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
|
||||||
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
|
||||||
import { BitwardenToastModule } from "@bitwarden/angular/components/toastr.component";
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
|
||||||
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
|
||||||
import { AvatarModule, ButtonModule } from "@bitwarden/components";
|
import { AvatarModule, ButtonModule, ToastModule } from "@bitwarden/components";
|
||||||
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
|
import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui";
|
||||||
|
|
||||||
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
|
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
|
||||||
@ -87,7 +86,7 @@ import "../platform/popup/locales";
|
|||||||
imports: [
|
imports: [
|
||||||
A11yModule,
|
A11yModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BitwardenToastModule.forRoot({
|
ToastModule.forRoot({
|
||||||
maxOpened: 2,
|
maxOpened: 2,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
@import "~ngx-toastr/toastr";
|
|
||||||
|
|
||||||
@import "variables.scss";
|
|
||||||
@import "buttons.scss";
|
|
||||||
|
|
||||||
// Toaster
|
|
||||||
|
|
||||||
.toast-container {
|
|
||||||
.toast-close-button {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("toastTextColor");
|
|
||||||
}
|
|
||||||
font-size: 18px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ngx-toastr {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("toastTextColor");
|
|
||||||
}
|
|
||||||
align-items: center;
|
|
||||||
background-image: none !important;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
|
|
||||||
display: flex;
|
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
.toast-close-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
float: left;
|
|
||||||
font-style: normal;
|
|
||||||
font-family: $icomoon-font-family;
|
|
||||||
font-size: 25px;
|
|
||||||
line-height: 20px;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-message {
|
|
||||||
p {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-danger,
|
|
||||||
&.toast-error {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("dangerColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
content: map_get($icons, "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-warning {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("warningColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
content: map_get($icons, "exclamation-triangle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-info {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("infoColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i:before {
|
|
||||||
content: map_get($icons, "info-circle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-success {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("successColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i:before {
|
|
||||||
content: map_get($icons, "check");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,6 @@
|
|||||||
@import "buttons.scss";
|
@import "buttons.scss";
|
||||||
@import "misc.scss";
|
@import "misc.scss";
|
||||||
@import "modal.scss";
|
@import "modal.scss";
|
||||||
@import "plugins.scss";
|
|
||||||
@import "environment.scss";
|
@import "environment.scss";
|
||||||
@import "pages.scss";
|
@import "pages.scss";
|
||||||
@import "@angular/cdk/overlay-prebuilt.css";
|
@import "@angular/cdk/overlay-prebuilt.css";
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { ToastrService } from "ngx-toastr";
|
|
||||||
|
|
||||||
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
||||||
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
||||||
@ -83,7 +81,7 @@ import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vau
|
|||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
|
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||||
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { UnauthGuardService } from "../../auth/popup/services";
|
import { UnauthGuardService } from "../../auth/popup/services";
|
||||||
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
|
||||||
@ -259,15 +257,9 @@ const safeProviders: SafeProvider[] = [
|
|||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: PlatformUtilsService,
|
provide: PlatformUtilsService,
|
||||||
useExisting: ForegroundPlatformUtilsService,
|
useFactory: (toastService: ToastService) => {
|
||||||
}),
|
|
||||||
safeProvider({
|
|
||||||
provide: ForegroundPlatformUtilsService,
|
|
||||||
useClass: ForegroundPlatformUtilsService,
|
|
||||||
useFactory: (sanitizer: DomSanitizer, toastrService: ToastrService) => {
|
|
||||||
return new ForegroundPlatformUtilsService(
|
return new ForegroundPlatformUtilsService(
|
||||||
sanitizer,
|
toastService,
|
||||||
toastrService,
|
|
||||||
(clipboardValue: string, clearMs: number) => {
|
(clipboardValue: string, clearMs: number) => {
|
||||||
void BrowserApi.sendMessage("clearClipboard", { clipboardValue, clearMs });
|
void BrowserApi.sendMessage("clearClipboard", { clipboardValue, clearMs });
|
||||||
},
|
},
|
||||||
@ -284,7 +276,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
window,
|
window,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
deps: [DomSanitizer, ToastrService],
|
deps: [ToastService],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: PasswordGenerationServiceAbstraction,
|
provide: PasswordGenerationServiceAbstraction,
|
||||||
|
@ -3,14 +3,11 @@ import {
|
|||||||
NgZone,
|
NgZone,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
SecurityContext,
|
|
||||||
Type,
|
Type,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
|
||||||
import { firstValueFrom, Subject, takeUntil } from "rxjs";
|
import { firstValueFrom, Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||||
@ -49,7 +46,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti
|
|||||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
import { DeleteAccountComponent } from "../auth/delete-account.component";
|
||||||
import { LoginApprovalComponent } from "../auth/login/login-approval.component";
|
import { LoginApprovalComponent } from "../auth/login/login-approval.component";
|
||||||
@ -129,9 +126,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private toastrService: ToastrService,
|
private toastService: ToastService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private sanitizer: DomSanitizer,
|
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
@ -294,7 +290,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "showToast":
|
case "showToast":
|
||||||
this.showToast(message);
|
this.toastService._showToast(message);
|
||||||
break;
|
break;
|
||||||
case "copiedToClipboard":
|
case "copiedToClipboard":
|
||||||
if (!message.clearing) {
|
if (!message.clearing) {
|
||||||
@ -674,34 +670,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private showToast(msg: any) {
|
|
||||||
let message = "";
|
|
||||||
|
|
||||||
const options: Partial<IndividualConfig> = {};
|
|
||||||
|
|
||||||
if (typeof msg.text === "string") {
|
|
||||||
message = msg.text;
|
|
||||||
} else if (msg.text.length === 1) {
|
|
||||||
message = msg.text[0];
|
|
||||||
} else {
|
|
||||||
msg.text.forEach(
|
|
||||||
(t: string) =>
|
|
||||||
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>"),
|
|
||||||
);
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options != null) {
|
|
||||||
if (msg.options.trustedHtml === true) {
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
|
||||||
options.timeOut = msg.options.timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private routeToVault(action: string, cipherType: CipherType) {
|
private routeToVault(action: string, cipherType: CipherType) {
|
||||||
if (!this.router.url.includes("vault")) {
|
if (!this.router.url.includes("vault")) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
@import "~ngx-toastr/toastr";
|
|
||||||
|
|
||||||
@import "variables.scss";
|
|
||||||
|
|
||||||
.toast-container {
|
|
||||||
.toast-close-button {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("toastTextColor");
|
|
||||||
}
|
|
||||||
font-size: 18px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ngx-toastr {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("toastTextColor");
|
|
||||||
}
|
|
||||||
align-items: center;
|
|
||||||
background-image: none !important;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
|
|
||||||
display: flex;
|
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
.toast-close-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
float: left;
|
|
||||||
font-style: normal;
|
|
||||||
font-family: $icomoon-font-family;
|
|
||||||
font-size: 25px;
|
|
||||||
line-height: 20px;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-message {
|
|
||||||
p {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-danger,
|
|
||||||
&.toast-error {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("dangerColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
content: map_get($icons, "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-warning {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("warningColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
content: map_get($icons, "exclamation-triangle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-info {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("infoColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i:before {
|
|
||||||
content: map_get($icons, "info-circle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-success {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("successColor");
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i:before {
|
|
||||||
content: map_get($icons, "check");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,7 +11,6 @@
|
|||||||
@import "buttons.scss";
|
@import "buttons.scss";
|
||||||
@import "misc.scss";
|
@import "misc.scss";
|
||||||
@import "modal.scss";
|
@import "modal.scss";
|
||||||
@import "plugins.scss";
|
|
||||||
@import "environment.scss";
|
@import "environment.scss";
|
||||||
@import "header.scss";
|
@import "header.scss";
|
||||||
@import "left-nav.scss";
|
@import "left-nav.scss";
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { DOCUMENT } from "@angular/common";
|
import { DOCUMENT } from "@angular/common";
|
||||||
import { Component, Inject, NgZone, OnDestroy, OnInit, SecurityContext } from "@angular/core";
|
import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { NavigationEnd, Router } from "@angular/router";
|
import { NavigationEnd, Router } from "@angular/router";
|
||||||
import * as jq from "jquery";
|
import * as jq from "jquery";
|
||||||
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
|
||||||
import { Subject, switchMap, takeUntil, timer } from "rxjs";
|
import { Subject, switchMap, takeUntil, timer } from "rxjs";
|
||||||
|
|
||||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
@ -29,7 +27,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
|||||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { PolicyListService } from "./admin-console/core/policy-list.service";
|
import { PolicyListService } from "./admin-console/core/policy-list.service";
|
||||||
import {
|
import {
|
||||||
@ -68,14 +66,13 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private toastrService: ToastrService,
|
private toastService: ToastService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private collectionService: CollectionService,
|
private collectionService: CollectionService,
|
||||||
private sanitizer: DomSanitizer,
|
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
@ -209,7 +206,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "showToast":
|
case "showToast":
|
||||||
this.showToast(message);
|
this.toastService._showToast(message);
|
||||||
break;
|
break;
|
||||||
case "convertAccountToKeyConnector":
|
case "convertAccountToKeyConnector":
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
@ -327,34 +324,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
}, IdleTimeout);
|
}, IdleTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private showToast(msg: any) {
|
|
||||||
let message = "";
|
|
||||||
|
|
||||||
const options: Partial<IndividualConfig> = {};
|
|
||||||
|
|
||||||
if (typeof msg.text === "string") {
|
|
||||||
message = msg.text;
|
|
||||||
} else if (msg.text.length === 1) {
|
|
||||||
message = msg.text[0];
|
|
||||||
} else {
|
|
||||||
msg.text.forEach(
|
|
||||||
(t: string) =>
|
|
||||||
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>"),
|
|
||||||
);
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options != null) {
|
|
||||||
if (msg.options.trustedHtml === true) {
|
|
||||||
options.enableHtml = true;
|
|
||||||
}
|
|
||||||
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
|
||||||
options.timeOut = msg.options.timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private idleStateChanged() {
|
private idleStateChanged() {
|
||||||
if (this.isIdle) {
|
if (this.isIdle) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
|
@ -4,7 +4,6 @@ import { NgModule } from "@angular/core";
|
|||||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
||||||
import { ToastrModule } from "ngx-toastr";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import {
|
import {
|
||||||
@ -52,7 +51,6 @@ import "./locales";
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
ToastrModule,
|
|
||||||
JslibModule,
|
JslibModule,
|
||||||
|
|
||||||
// Component library modules
|
// Component library modules
|
||||||
@ -90,7 +88,6 @@ import "./locales";
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
InfiniteScrollModule,
|
InfiniteScrollModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
ToastrModule,
|
|
||||||
JslibModule,
|
JslibModule,
|
||||||
|
|
||||||
// Component library
|
// Component library
|
||||||
|
@ -7606,6 +7606,9 @@
|
|||||||
"providerPortal": {
|
"providerPortal": {
|
||||||
"message": "Provider Portal"
|
"message": "Provider Portal"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"viewCollection": {
|
"viewCollection": {
|
||||||
"message": "View collection"
|
"message": "View collection"
|
||||||
},
|
},
|
||||||
|
@ -43,8 +43,6 @@
|
|||||||
@import "~bootstrap/scss/_utilities";
|
@import "~bootstrap/scss/_utilities";
|
||||||
@import "~bootstrap/scss/_print";
|
@import "~bootstrap/scss/_print";
|
||||||
|
|
||||||
@import "~ngx-toastr/toastr";
|
|
||||||
|
|
||||||
@import "./base";
|
@import "./base";
|
||||||
@import "./buttons";
|
@import "./buttons";
|
||||||
@import "./callouts";
|
@import "./callouts";
|
||||||
@ -54,5 +52,4 @@
|
|||||||
@import "./pages";
|
@import "./pages";
|
||||||
@import "./plugins";
|
@import "./plugins";
|
||||||
@import "./tables";
|
@import "./tables";
|
||||||
@import "./toasts";
|
|
||||||
@import "./vault-filters";
|
@import "./vault-filters";
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
.toast-container {
|
|
||||||
.toast-close-button {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ngx-toastr {
|
|
||||||
align-items: center;
|
|
||||||
background-image: none !important;
|
|
||||||
border-radius: $border-radius;
|
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.35);
|
|
||||||
display: flex;
|
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
.toast-close-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
float: left;
|
|
||||||
font-style: normal;
|
|
||||||
font-family: $icomoon-font-family;
|
|
||||||
font-size: 25px;
|
|
||||||
line-height: 20px;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-message {
|
|
||||||
p {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-danger,
|
|
||||||
&.toast-error {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("danger");
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
&:before,
|
|
||||||
& .toast-close-button {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("textDangerColor") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
content: map_get($icons, "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-warning {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("warning");
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
&:before,
|
|
||||||
& .toast-close-button {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("textWarningColor") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i::before {
|
|
||||||
content: map_get($icons, "exclamation-triangle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-info {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("info");
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
&:before,
|
|
||||||
& .toast-close-button {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("textInfoColor") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i:before {
|
|
||||||
content: map_get($icons, "info-circle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toast-success {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed("success");
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
&:before,
|
|
||||||
& .toast-close-button {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed("textSuccessColor") !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon i:before {
|
|
||||||
content: map_get($icons, "check");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
|
||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { Component, ModuleWithProviders, NgModule } from "@angular/core";
|
|
||||||
import {
|
|
||||||
DefaultNoComponentGlobalConfig,
|
|
||||||
GlobalConfig,
|
|
||||||
Toast as BaseToast,
|
|
||||||
ToastPackage,
|
|
||||||
ToastrService,
|
|
||||||
TOAST_CONFIG,
|
|
||||||
} from "ngx-toastr";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "[toast-component2]",
|
|
||||||
template: `
|
|
||||||
<button
|
|
||||||
*ngIf="options.closeButton"
|
|
||||||
(click)="remove()"
|
|
||||||
type="button"
|
|
||||||
class="toast-close-button"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<div class="icon">
|
|
||||||
<i></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div *ngIf="title" [class]="options.titleClass" [attr.aria-label]="title">
|
|
||||||
{{ title }} <ng-container *ngIf="duplicatesCount">[{{ duplicatesCount + 1 }}]</ng-container>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
*ngIf="message && options.enableHtml"
|
|
||||||
role="alertdialog"
|
|
||||||
aria-live="polite"
|
|
||||||
[class]="options.messageClass"
|
|
||||||
[innerHTML]="message"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
*ngIf="message && !options.enableHtml"
|
|
||||||
role="alertdialog"
|
|
||||||
aria-live="polite"
|
|
||||||
[class]="options.messageClass"
|
|
||||||
[attr.aria-label]="message"
|
|
||||||
>
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="options.progressBar">
|
|
||||||
<div class="toast-progress" [style.width]="width + '%'"></div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
animations: [
|
|
||||||
trigger("flyInOut", [
|
|
||||||
state("inactive", style({ opacity: 0 })),
|
|
||||||
state("active", style({ opacity: 1 })),
|
|
||||||
state("removed", style({ opacity: 0 })),
|
|
||||||
transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")),
|
|
||||||
transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
preserveWhitespaces: false,
|
|
||||||
})
|
|
||||||
export class BitwardenToast extends BaseToast {
|
|
||||||
constructor(
|
|
||||||
protected toastrService: ToastrService,
|
|
||||||
public toastPackage: ToastPackage,
|
|
||||||
) {
|
|
||||||
super(toastrService, toastPackage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BitwardenToastGlobalConfig: GlobalConfig = {
|
|
||||||
...DefaultNoComponentGlobalConfig,
|
|
||||||
toastComponent: BitwardenToast,
|
|
||||||
};
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule],
|
|
||||||
declarations: [BitwardenToast],
|
|
||||||
exports: [BitwardenToast],
|
|
||||||
})
|
|
||||||
export class BitwardenToastModule {
|
|
||||||
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<BitwardenToastModule> {
|
|
||||||
return {
|
|
||||||
ngModule: BitwardenToastModule,
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: TOAST_CONFIG,
|
|
||||||
useValue: {
|
|
||||||
default: BitwardenToastGlobalConfig,
|
|
||||||
config: config,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,10 +2,9 @@ import { CommonModule, DatePipe } from "@angular/common";
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
|
||||||
import { AutofocusDirective } from "@bitwarden/components";
|
import { AutofocusDirective, ToastModule } from "@bitwarden/components";
|
||||||
|
|
||||||
import { CalloutComponent } from "./components/callout.component";
|
import { CalloutComponent } from "./components/callout.component";
|
||||||
import { BitwardenToastModule } from "./components/toastr.component";
|
|
||||||
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
|
import { A11yInvalidDirective } from "./directives/a11y-invalid.directive";
|
||||||
import { A11yTitleDirective } from "./directives/a11y-title.directive";
|
import { A11yTitleDirective } from "./directives/a11y-title.directive";
|
||||||
import { ApiActionDirective } from "./directives/api-action.directive";
|
import { ApiActionDirective } from "./directives/api-action.directive";
|
||||||
@ -34,7 +33,7 @@ import { IconComponent } from "./vault/components/icon.component";
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BitwardenToastModule.forRoot({
|
ToastModule.forRoot({
|
||||||
maxOpened: 5,
|
maxOpened: 5,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
@ -77,7 +76,7 @@ import { IconComponent } from "./vault/components/icon.component";
|
|||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BitwardenToastModule,
|
ToastModule,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
CalloutComponent,
|
CalloutComponent,
|
||||||
CopyTextDirective,
|
CopyTextDirective,
|
||||||
|
@ -28,6 +28,11 @@ export abstract class PlatformUtilsService {
|
|||||||
abstract getApplicationVersionNumber(): Promise<string>;
|
abstract getApplicationVersionNumber(): Promise<string>;
|
||||||
abstract supportsWebAuthn(win: Window): boolean;
|
abstract supportsWebAuthn(win: Window): boolean;
|
||||||
abstract supportsDuo(): boolean;
|
abstract supportsDuo(): boolean;
|
||||||
|
/**
|
||||||
|
* @deprecated use `@bitwarden/components/ToastService.showToast` instead
|
||||||
|
*
|
||||||
|
* Jira: [CL-213](https://bitwarden.atlassian.net/browse/CL-213)
|
||||||
|
*/
|
||||||
abstract showToast(
|
abstract showToast(
|
||||||
type: "error" | "success" | "warning" | "info",
|
type: "error" | "success" | "warning" | "info",
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -29,6 +29,7 @@ export * from "./section";
|
|||||||
export * from "./select";
|
export * from "./select";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
export * from "./tabs";
|
export * from "./tabs";
|
||||||
|
export * from "./toast";
|
||||||
export * from "./toggle-group";
|
export * from "./toggle-group";
|
||||||
export * from "./typography";
|
export * from "./typography";
|
||||||
export * from "./utils/i18n-mock.service";
|
export * from "./utils/i18n-mock.service";
|
||||||
|
2
libs/components/src/toast/index.ts
Normal file
2
libs/components/src/toast/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./toast.module";
|
||||||
|
export * from "./toast.service";
|
24
libs/components/src/toast/toast.component.html
Normal file
24
libs/components/src/toast/toast.component.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<div
|
||||||
|
class="tw-mb-1 tw-min-w-[--bit-toast-width] tw-text-contrast tw-flex tw-flex-col tw-justify-between tw-rounded-md tw-pointer-events-auto tw-cursor-default {{
|
||||||
|
bgColor
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<div class="tw-flex tw-items-center tw-gap-4 tw-px-2 tw-pb-1 tw-pt-2">
|
||||||
|
<i aria-hidden="true" class="bwi tw-text-xl tw-py-1.5 tw-px-2.5 {{ iconClass }}"></i>
|
||||||
|
<div>
|
||||||
|
<span class="tw-sr-only">{{ variant | i18n }}</span>
|
||||||
|
<p *ngIf="title" data-testid="toast-title" class="tw-font-semibold tw-mb-0">{{ title }}</p>
|
||||||
|
<p *ngFor="let m of messageArray" data-testid="toast-message" class="tw-mb-2 last:tw-mb-0">
|
||||||
|
{{ m }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="tw-ml-auto"
|
||||||
|
bitIconButton="bwi-close"
|
||||||
|
buttonType="contrast"
|
||||||
|
type="button"
|
||||||
|
(click)="this.onClose.emit()"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
<div class="tw-h-1 tw-w-full tw-bg-text-contrast/70" [style.width]="progressWidth + '%'"></div>
|
||||||
|
</div>
|
66
libs/components/src/toast/toast.component.ts
Normal file
66
libs/components/src/toast/toast.component.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
|
|
||||||
|
import { IconButtonModule } from "../icon-button";
|
||||||
|
import { SharedModule } from "../shared";
|
||||||
|
|
||||||
|
export type ToastVariant = "success" | "error" | "info" | "warning";
|
||||||
|
|
||||||
|
const variants: Record<ToastVariant, { icon: string; bgColor: string }> = {
|
||||||
|
success: {
|
||||||
|
icon: "bwi-check",
|
||||||
|
bgColor: "tw-bg-success-600",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
icon: "bwi-error",
|
||||||
|
bgColor: "tw-bg-danger-600",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
icon: "bwi-info-circle",
|
||||||
|
bgColor: "tw-bg-info-600",
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
icon: "bwi-exclamation-triangle",
|
||||||
|
bgColor: "tw-bg-warning-600",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-toast",
|
||||||
|
templateUrl: "toast.component.html",
|
||||||
|
standalone: true,
|
||||||
|
imports: [SharedModule, IconButtonModule],
|
||||||
|
})
|
||||||
|
export class ToastComponent {
|
||||||
|
@Input() variant: ToastVariant = "info";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message to display
|
||||||
|
*
|
||||||
|
* Pass an array to render multiple paragraphs.
|
||||||
|
**/
|
||||||
|
@Input({ required: true })
|
||||||
|
message: string | string[];
|
||||||
|
|
||||||
|
/** An optional title to display over the message. */
|
||||||
|
@Input() title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The percent width of the progress bar, from 0-100
|
||||||
|
**/
|
||||||
|
@Input() progressWidth = 0;
|
||||||
|
|
||||||
|
/** Emits when the user presses the close button */
|
||||||
|
@Output() onClose = new EventEmitter<void>();
|
||||||
|
|
||||||
|
protected get iconClass(): string {
|
||||||
|
return variants[this.variant].icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get bgColor(): string {
|
||||||
|
return variants[this.variant].bgColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get messageArray(): string[] {
|
||||||
|
return Array.isArray(this.message) ? this.message : [this.message];
|
||||||
|
}
|
||||||
|
}
|
39
libs/components/src/toast/toast.module.ts
Normal file
39
libs/components/src/toast/toast.module.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { ModuleWithProviders, NgModule } from "@angular/core";
|
||||||
|
import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr";
|
||||||
|
|
||||||
|
import { ToastComponent } from "./toast.component";
|
||||||
|
import { BitwardenToastrComponent } from "./toastr.component";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule, ToastComponent],
|
||||||
|
declarations: [BitwardenToastrComponent],
|
||||||
|
exports: [BitwardenToastrComponent],
|
||||||
|
})
|
||||||
|
export class ToastModule {
|
||||||
|
static forRoot(config: Partial<GlobalConfig> = {}): ModuleWithProviders<ToastModule> {
|
||||||
|
return {
|
||||||
|
ngModule: ToastModule,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: TOAST_CONFIG,
|
||||||
|
useValue: {
|
||||||
|
default: BitwardenToastrGlobalConfig,
|
||||||
|
config: config,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BitwardenToastrGlobalConfig: GlobalConfig = {
|
||||||
|
...DefaultNoComponentGlobalConfig,
|
||||||
|
toastComponent: BitwardenToastrComponent,
|
||||||
|
tapToDismiss: false,
|
||||||
|
timeOut: 5000,
|
||||||
|
extendedTimeOut: 2000,
|
||||||
|
maxOpened: 5,
|
||||||
|
autoDismiss: true,
|
||||||
|
progressBar: true,
|
||||||
|
};
|
57
libs/components/src/toast/toast.service.ts
Normal file
57
libs/components/src/toast/toast.service.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||||
|
|
||||||
|
import type { ToastComponent } from "./toast.component";
|
||||||
|
import { calculateToastTimeout } from "./utils";
|
||||||
|
|
||||||
|
export type ToastOptions = {
|
||||||
|
/**
|
||||||
|
* The duration the toast will persist in milliseconds
|
||||||
|
**/
|
||||||
|
timeout?: number;
|
||||||
|
} & Pick<ToastComponent, "message" | "variant" | "title">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presents toast notifications
|
||||||
|
**/
|
||||||
|
@Injectable({ providedIn: "root" })
|
||||||
|
export class ToastService {
|
||||||
|
constructor(private toastrService: ToastrService) {}
|
||||||
|
|
||||||
|
showToast(options: ToastOptions) {
|
||||||
|
const toastrConfig: Partial<IndividualConfig> = {
|
||||||
|
payload: {
|
||||||
|
message: options.message,
|
||||||
|
variant: options.variant,
|
||||||
|
title: options.title,
|
||||||
|
},
|
||||||
|
timeOut:
|
||||||
|
options.timeout != null && options.timeout > 0
|
||||||
|
? options.timeout
|
||||||
|
: calculateToastTimeout(options.message),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.toastrService.show(null, options.title, toastrConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `showToast` instead
|
||||||
|
*
|
||||||
|
* Converts options object from PlatformUtilsService
|
||||||
|
**/
|
||||||
|
_showToast(options: {
|
||||||
|
type: "error" | "success" | "warning" | "info";
|
||||||
|
title: string;
|
||||||
|
text: string | string[];
|
||||||
|
options?: {
|
||||||
|
timeout?: number;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
this.showToast({
|
||||||
|
message: options.text,
|
||||||
|
variant: options.type,
|
||||||
|
title: options.title,
|
||||||
|
timeout: options.options?.timeout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
16
libs/components/src/toast/toast.spec.ts
Normal file
16
libs/components/src/toast/toast.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { calculateToastTimeout } from "./utils";
|
||||||
|
|
||||||
|
describe("Toast default timer", () => {
|
||||||
|
it("should have a minimum of 5000ms", () => {
|
||||||
|
expect(calculateToastTimeout("")).toBe(5000);
|
||||||
|
expect(calculateToastTimeout([""])).toBe(5000);
|
||||||
|
expect(calculateToastTimeout(" ")).toBe(5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return an extra second for each 120 words", () => {
|
||||||
|
expect(calculateToastTimeout("foo ".repeat(119))).toBe(5000);
|
||||||
|
expect(calculateToastTimeout("foo ".repeat(120))).toBe(6000);
|
||||||
|
expect(calculateToastTimeout("foo ".repeat(240))).toBe(7000);
|
||||||
|
expect(calculateToastTimeout(["foo ".repeat(120), " \n foo ".repeat(120)])).toBe(7000);
|
||||||
|
});
|
||||||
|
});
|
124
libs/components/src/toast/toast.stories.ts
Normal file
124
libs/components/src/toast/toast.stories.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
import { action } from "@storybook/addon-actions";
|
||||||
|
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
import { ButtonModule } from "../button";
|
||||||
|
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||||
|
|
||||||
|
import { ToastComponent } from "./toast.component";
|
||||||
|
import { BitwardenToastrGlobalConfig, ToastModule } from "./toast.module";
|
||||||
|
import { ToastOptions, ToastService } from "./toast.service";
|
||||||
|
|
||||||
|
const toastServiceExampleTemplate = `
|
||||||
|
<button bitButton type="button" (click)="toastService.showToast(toastOptions)">Show Toast</button>
|
||||||
|
`;
|
||||||
|
@Component({
|
||||||
|
selector: "toast-service-example",
|
||||||
|
template: toastServiceExampleTemplate,
|
||||||
|
})
|
||||||
|
export class ToastServiceExampleComponent {
|
||||||
|
@Input()
|
||||||
|
toastOptions: ToastOptions;
|
||||||
|
|
||||||
|
constructor(protected toastService: ToastService) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Component Library/Toast",
|
||||||
|
component: ToastComponent,
|
||||||
|
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [CommonModule, BrowserAnimationsModule, ButtonModule],
|
||||||
|
declarations: [ToastServiceExampleComponent],
|
||||||
|
}),
|
||||||
|
applicationConfig({
|
||||||
|
providers: [
|
||||||
|
ToastModule.forRoot().providers,
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
close: "Close",
|
||||||
|
success: "Success",
|
||||||
|
error: "Error",
|
||||||
|
warning: "Warning",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
onClose: action("emit onClose"),
|
||||||
|
variant: "info",
|
||||||
|
progressWidth: 50,
|
||||||
|
title: "",
|
||||||
|
message: "Hello Bitwarden!",
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: "figma",
|
||||||
|
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<ToastComponent>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `
|
||||||
|
<div class="tw-flex tw-flex-col tw-min-w tw-max-w-[--bit-toast-width]">
|
||||||
|
<bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="success"></bit-toast>
|
||||||
|
<bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="info"></bit-toast>
|
||||||
|
<bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="warning"></bit-toast>
|
||||||
|
<bit-toast [title]="title" [message]="message" [progressWidth]="progressWidth" (onClose)="onClose()" variant="error"></bit-toast>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Avoid using long messages in toasts.
|
||||||
|
*/
|
||||||
|
export const LongContent: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
title: "Foo",
|
||||||
|
message: [
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipisci",
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipisci",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Service: Story = {
|
||||||
|
render: (args) => ({
|
||||||
|
props: {
|
||||||
|
toastOptions: args,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<toast-service-example [toastOptions]="toastOptions"></toast-service-example>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
args: {
|
||||||
|
title: "",
|
||||||
|
message: "Hello Bitwarden!",
|
||||||
|
variant: "error",
|
||||||
|
timeout: BitwardenToastrGlobalConfig.timeOut,
|
||||||
|
} as ToastOptions,
|
||||||
|
parameters: {
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
docs: {
|
||||||
|
source: {
|
||||||
|
code: toastServiceExampleTemplate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
4
libs/components/src/toast/toast.tokens.css
Normal file
4
libs/components/src/toast/toast.tokens.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
:root {
|
||||||
|
--bit-toast-width: 19rem;
|
||||||
|
--bit-toast-width-full: 96%;
|
||||||
|
}
|
26
libs/components/src/toast/toastr.component.ts
Normal file
26
libs/components/src/toast/toastr.component.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { Toast as BaseToastrComponent } from "ngx-toastr";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<bit-toast
|
||||||
|
[title]="options?.payload?.title"
|
||||||
|
[variant]="options?.payload?.variant"
|
||||||
|
[message]="options?.payload?.message"
|
||||||
|
[progressWidth]="width"
|
||||||
|
(onClose)="remove()"
|
||||||
|
></bit-toast>
|
||||||
|
`,
|
||||||
|
animations: [
|
||||||
|
trigger("flyInOut", [
|
||||||
|
state("inactive", style({ opacity: 0 })),
|
||||||
|
state("active", style({ opacity: 1 })),
|
||||||
|
state("removed", style({ opacity: 0 })),
|
||||||
|
transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")),
|
||||||
|
transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
preserveWhitespaces: false,
|
||||||
|
})
|
||||||
|
export class BitwardenToastrComponent extends BaseToastrComponent {}
|
23
libs/components/src/toast/toastr.css
Normal file
23
libs/components/src/toast/toastr.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
@import "~ngx-toastr/toastr";
|
||||||
|
@import "./toast.tokens.css";
|
||||||
|
|
||||||
|
/* Override all default styles from `ngx-toaster` */
|
||||||
|
.toast-container .ngx-toastr {
|
||||||
|
all: unset;
|
||||||
|
display: block;
|
||||||
|
width: var(--bit-toast-width);
|
||||||
|
|
||||||
|
/* Needed to make hover states work in Electron, since the toast appears in the draggable region. */
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable hover styles */
|
||||||
|
.toast-container .ngx-toastr:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container.toast-bottom-full-width .ngx-toastr {
|
||||||
|
width: var(--bit-toast-width-full);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
14
libs/components/src/toast/utils.ts
Normal file
14
libs/components/src/toast/utils.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Given a toast message, calculate the ideal timeout length following:
|
||||||
|
* a minimum of 5 seconds + 1 extra second per 120 additional words
|
||||||
|
*
|
||||||
|
* @param message the toast message to be displayed
|
||||||
|
* @returns the timeout length in milliseconds
|
||||||
|
*/
|
||||||
|
export const calculateToastTimeout = (message: string | string[]): number => {
|
||||||
|
const paragraphs = Array.isArray(message) ? message : [message];
|
||||||
|
const numWords = paragraphs
|
||||||
|
.map((paragraph) => paragraph.split(/\s+/).filter((word) => word !== ""))
|
||||||
|
.flat().length;
|
||||||
|
return 5000 + Math.floor(numWords / 120) * 1000;
|
||||||
|
};
|
@ -171,6 +171,9 @@
|
|||||||
@import "./popover/popover.component.css";
|
@import "./popover/popover.component.css";
|
||||||
@import "./search/search.component.css";
|
@import "./search/search.component.css";
|
||||||
|
|
||||||
|
@import "./toast/toast.tokens.css";
|
||||||
|
@import "./toast/toastr.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tw-break-words does not work with table cells:
|
* tw-break-words does not work with table cells:
|
||||||
* https://github.com/tailwindlabs/tailwindcss/issues/835
|
* https://github.com/tailwindlabs/tailwindcss/issues/835
|
||||||
|
Loading…
Reference in New Issue
Block a user