mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
PM-18276-Connect confirmation UI (#13498)
* PM-18276-wip * update typing * dynamically retrieve messages, resolve theme in function * five second timeout after save or update * adjust timeout to five seconds * negligible performance gain-revert * sacrifice contorl for to remove event listeners-revert
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { css } from "@emotion/css";
|
||||
import { html } from "lit";
|
||||
|
||||
import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||
|
||||
import {
|
||||
NotificationBarIframeInitData,
|
||||
NotificationTypes,
|
||||
NotificationType,
|
||||
} from "../../../notification/abstractions/notification-bar";
|
||||
import { themes, spacing } from "../constants/styles";
|
||||
|
||||
import { NotificationConfirmationBody } from "./confirmation";
|
||||
import {
|
||||
NotificationHeader,
|
||||
componentClassPrefix as notificationHeaderClassPrefix,
|
||||
} from "./header";
|
||||
|
||||
export function NotificationConfirmationContainer({
|
||||
error,
|
||||
handleCloseNotification,
|
||||
i18n,
|
||||
theme = ThemeTypes.Light,
|
||||
type,
|
||||
}: NotificationBarIframeInitData & {
|
||||
handleCloseNotification: (e: Event) => void;
|
||||
} & {
|
||||
error: string;
|
||||
i18n: { [key: string]: string };
|
||||
type: NotificationType;
|
||||
}) {
|
||||
const headerMessage = getHeaderMessage(i18n, type, error);
|
||||
const confirmationMessage = getConfirmationMessage(i18n, type, error);
|
||||
const buttonText = error ? i18n.newItem : i18n.view;
|
||||
|
||||
return html`
|
||||
<div class=${notificationContainerStyles(theme)}>
|
||||
${NotificationHeader({
|
||||
handleCloseNotification,
|
||||
message: headerMessage,
|
||||
theme,
|
||||
})}
|
||||
${NotificationConfirmationBody({
|
||||
error: error,
|
||||
buttonText,
|
||||
confirmationMessage,
|
||||
theme,
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const notificationContainerStyles = (theme: Theme) => css`
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
border: 1px solid ${themes[theme].secondary["300"]};
|
||||
border-radius: ${spacing["4"]};
|
||||
box-shadow: -2px 4px 6px 0px #0000001a;
|
||||
background-color: ${themes[theme].background.alt};
|
||||
width: 400px;
|
||||
overflow: hidden;
|
||||
|
||||
[class*="${notificationHeaderClassPrefix}-"] {
|
||||
border-radius: ${spacing["4"]} ${spacing["4"]} 0 0;
|
||||
border-bottom: 0.5px solid ${themes[theme].secondary["300"]};
|
||||
}
|
||||
`;
|
||||
|
||||
function getConfirmationMessage(
|
||||
i18n: { [key: string]: string },
|
||||
type?: NotificationType,
|
||||
error?: string,
|
||||
) {
|
||||
if (error) {
|
||||
return i18n.saveFailureDetails;
|
||||
}
|
||||
return type === "add" ? i18n.loginSaveSuccessDetails : i18n.loginUpdateSuccessDetails;
|
||||
}
|
||||
function getHeaderMessage(
|
||||
i18n: { [key: string]: string },
|
||||
type?: NotificationType,
|
||||
error?: string,
|
||||
) {
|
||||
if (error) {
|
||||
return i18n.saveFailure;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NotificationTypes.Add:
|
||||
return i18n.loginSaveSuccess;
|
||||
case NotificationTypes.Change:
|
||||
return i18n.loginUpdateSuccess;
|
||||
case NotificationTypes.Unlock:
|
||||
return "";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@ const { css } = createEmotion({
|
||||
|
||||
export function NotificationHeader({
|
||||
message,
|
||||
standalone,
|
||||
standalone = false,
|
||||
theme = ThemeTypes.Light,
|
||||
handleCloseNotification,
|
||||
}: {
|
||||
message?: string;
|
||||
standalone: boolean;
|
||||
standalone?: boolean;
|
||||
theme: Theme;
|
||||
handleCloseNotification: (e: Event) => void;
|
||||
}) {
|
||||
@@ -49,7 +49,7 @@ const notificationHeaderStyles = ({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background-color: ${themes[theme].background.alt};
|
||||
background-color: ${themes[theme].background};
|
||||
padding: 12px 16px 8px 16px;
|
||||
white-space: nowrap;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
||||
import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background";
|
||||
import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container";
|
||||
import { NotificationContainer } from "../content/components/notification/container";
|
||||
import { buildSvgDomElement } from "../utils";
|
||||
import { circleCheckIcon } from "../utils/svg-icons";
|
||||
@@ -22,12 +23,17 @@ const logService = new ConsoleLogService(false);
|
||||
let notificationBarIframeInitData: NotificationBarIframeInitData = {};
|
||||
let windowMessageOrigin: string;
|
||||
let useComponentBar = false;
|
||||
|
||||
const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers = {
|
||||
initNotificationBar: ({ message }) => initNotificationBar(message),
|
||||
saveCipherAttemptCompleted: ({ message }) => handleSaveCipherAttemptCompletedMessage(message),
|
||||
saveCipherAttemptCompleted: ({ message }) =>
|
||||
useComponentBar
|
||||
? handleSaveCipherConfirmation(message)
|
||||
: handleSaveCipherAttemptCompletedMessage(message),
|
||||
};
|
||||
|
||||
globalThis.addEventListener("load", load);
|
||||
|
||||
function load() {
|
||||
setupWindowMessageListener();
|
||||
sendPlatformMessage({ command: "notificationRefreshFlagValue" }, (flagValue) => {
|
||||
@@ -35,7 +41,6 @@ function load() {
|
||||
applyNotificationBarStyle();
|
||||
});
|
||||
}
|
||||
|
||||
function applyNotificationBarStyle() {
|
||||
if (!useComponentBar) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
@@ -44,16 +49,8 @@ function applyNotificationBarStyle() {
|
||||
postMessageToParent({ command: "initNotificationBar" });
|
||||
}
|
||||
|
||||
function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
const { initData } = message;
|
||||
if (!initData) {
|
||||
return;
|
||||
}
|
||||
|
||||
notificationBarIframeInitData = initData;
|
||||
const { isVaultLocked, theme } = notificationBarIframeInitData;
|
||||
|
||||
const i18n = {
|
||||
function getI18n() {
|
||||
return {
|
||||
appName: chrome.i18n.getMessage("appName"),
|
||||
close: chrome.i18n.getMessage("close"),
|
||||
never: chrome.i18n.getMessage("never"),
|
||||
@@ -74,20 +71,30 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
updateLoginPrompt: "Update existing login?",
|
||||
loginSaveSuccess: "Login saved",
|
||||
loginSaveSuccessDetails: "Login saved to Bitwarden.",
|
||||
loginUpdateSuccess: "Login saved",
|
||||
loginUpdateSuccess: "Login updated",
|
||||
loginUpdateSuccessDetails: "Login updated in Bitwarden.",
|
||||
saveFailure: "Error saving",
|
||||
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details as a New item",
|
||||
newItem: "New item",
|
||||
view: "View",
|
||||
};
|
||||
}
|
||||
|
||||
function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
const { initData } = message;
|
||||
if (!initData) {
|
||||
return;
|
||||
}
|
||||
|
||||
notificationBarIframeInitData = initData;
|
||||
const { isVaultLocked, theme } = notificationBarIframeInitData;
|
||||
const i18n = getI18n();
|
||||
const resolvedTheme = getResolvedTheme(theme);
|
||||
|
||||
if (useComponentBar) {
|
||||
document.body.innerHTML = "";
|
||||
// Current implementations utilize a require for scss files which creates the need to remove the node.
|
||||
document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove());
|
||||
const themeType = getTheme(globalThis, theme);
|
||||
|
||||
// There are other possible passed theme values, but for now, resolve to dark or light
|
||||
const resolvedTheme: Theme = themeType === ThemeTypes.Dark ? ThemeTypes.Dark : ThemeTypes.Light;
|
||||
|
||||
sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, (cipherData) => {
|
||||
// @TODO use context to avoid prop drilling
|
||||
@@ -105,77 +112,71 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
document.body,
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setNotificationBarTheme();
|
||||
|
||||
setNotificationBarTheme();
|
||||
(document.getElementById("logo") as HTMLImageElement).src = isVaultLocked
|
||||
? chrome.runtime.getURL("images/icon38_locked.png")
|
||||
: chrome.runtime.getURL("images/icon38.png");
|
||||
|
||||
(document.getElementById("logo") as HTMLImageElement).src = isVaultLocked
|
||||
? chrome.runtime.getURL("images/icon38_locked.png")
|
||||
: chrome.runtime.getURL("images/icon38.png");
|
||||
setupLogoLink(i18n);
|
||||
|
||||
setupLogoLink(i18n);
|
||||
// i18n for "Add" template
|
||||
const addTemplate = document.getElementById("template-add") as HTMLTemplateElement;
|
||||
|
||||
// i18n for "Add" template
|
||||
const addTemplate = document.getElementById("template-add") as HTMLTemplateElement;
|
||||
const neverButton = addTemplate.content.getElementById("never-save");
|
||||
neverButton.textContent = i18n.never;
|
||||
|
||||
const neverButton = addTemplate.content.getElementById("never-save");
|
||||
neverButton.textContent = i18n.never;
|
||||
const selectFolder = addTemplate.content.getElementById("select-folder");
|
||||
selectFolder.hidden = isVaultLocked || removeIndividualVault();
|
||||
selectFolder.setAttribute("aria-label", i18n.folder);
|
||||
|
||||
const selectFolder = addTemplate.content.getElementById("select-folder");
|
||||
selectFolder.hidden = isVaultLocked || removeIndividualVault();
|
||||
selectFolder.setAttribute("aria-label", i18n.folder);
|
||||
const addButton = addTemplate.content.getElementById("add-save");
|
||||
addButton.textContent = i18n.notificationAddSave;
|
||||
|
||||
const addButton = addTemplate.content.getElementById("add-save");
|
||||
addButton.textContent = i18n.notificationAddSave;
|
||||
const addEditButton = addTemplate.content.getElementById("add-edit");
|
||||
// If Remove Individual Vault policy applies, "Add" opens the edit tab, so we hide the Edit button
|
||||
addEditButton.hidden = removeIndividualVault();
|
||||
addEditButton.textContent = i18n.notificationEdit;
|
||||
|
||||
const addEditButton = addTemplate.content.getElementById("add-edit");
|
||||
// If Remove Individual Vault policy applies, "Add" opens the edit tab, so we hide the Edit button
|
||||
addEditButton.hidden = removeIndividualVault();
|
||||
addEditButton.textContent = i18n.notificationEdit;
|
||||
addTemplate.content.getElementById("add-text").textContent = i18n.notificationAddDesc;
|
||||
|
||||
addTemplate.content.getElementById("add-text").textContent = i18n.notificationAddDesc;
|
||||
// i18n for "Change" (update password) template
|
||||
const changeTemplate = document.getElementById("template-change") as HTMLTemplateElement;
|
||||
|
||||
// i18n for "Change" (update password) template
|
||||
const changeTemplate = document.getElementById("template-change") as HTMLTemplateElement;
|
||||
const changeButton = changeTemplate.content.getElementById("change-save");
|
||||
changeButton.textContent = i18n.notificationChangeSave;
|
||||
|
||||
const changeButton = changeTemplate.content.getElementById("change-save");
|
||||
changeButton.textContent = i18n.notificationChangeSave;
|
||||
const changeEditButton = changeTemplate.content.getElementById("change-edit");
|
||||
changeEditButton.textContent = i18n.notificationEdit;
|
||||
|
||||
const changeEditButton = changeTemplate.content.getElementById("change-edit");
|
||||
changeEditButton.textContent = i18n.notificationEdit;
|
||||
changeTemplate.content.getElementById("change-text").textContent = i18n.notificationChangeDesc;
|
||||
|
||||
changeTemplate.content.getElementById("change-text").textContent = i18n.notificationChangeDesc;
|
||||
// i18n for "Unlock" (unlock extension) template
|
||||
const unlockTemplate = document.getElementById("template-unlock") as HTMLTemplateElement;
|
||||
|
||||
// i18n for "Unlock" (unlock extension) template
|
||||
const unlockTemplate = document.getElementById("template-unlock") as HTMLTemplateElement;
|
||||
const unlockButton = unlockTemplate.content.getElementById("unlock-vault");
|
||||
unlockButton.textContent = i18n.notificationUnlock;
|
||||
|
||||
const unlockButton = unlockTemplate.content.getElementById("unlock-vault");
|
||||
unlockButton.textContent = i18n.notificationUnlock;
|
||||
unlockTemplate.content.getElementById("unlock-text").textContent = i18n.notificationUnlockDesc;
|
||||
|
||||
unlockTemplate.content.getElementById("unlock-text").textContent = i18n.notificationUnlockDesc;
|
||||
// i18n for body content
|
||||
const closeButton = document.getElementById("close-button");
|
||||
closeButton.title = i18n.close;
|
||||
|
||||
// i18n for body content
|
||||
const closeButton = document.getElementById("close-button");
|
||||
closeButton.title = i18n.close;
|
||||
const notificationType = initData.type;
|
||||
if (notificationType === "add") {
|
||||
handleTypeAdd();
|
||||
} else if (notificationType === "change") {
|
||||
handleTypeChange();
|
||||
} else if (notificationType === "unlock") {
|
||||
handleTypeUnlock();
|
||||
}
|
||||
|
||||
const notificationType = initData.type;
|
||||
if (notificationType === "add") {
|
||||
handleTypeAdd();
|
||||
} else if (notificationType === "change") {
|
||||
handleTypeChange();
|
||||
} else if (notificationType === "unlock") {
|
||||
handleTypeUnlock();
|
||||
}
|
||||
closeButton.addEventListener("click", handleCloseNotification);
|
||||
|
||||
closeButton.addEventListener("click", handleCloseNotification);
|
||||
|
||||
globalThis.addEventListener("resize", adjustHeight);
|
||||
adjustHeight();
|
||||
function handleCloseNotification(e: Event) {
|
||||
e.preventDefault();
|
||||
sendPlatformMessage({
|
||||
command: "bgCloseNotificationBar",
|
||||
});
|
||||
globalThis.addEventListener("resize", adjustHeight);
|
||||
adjustHeight();
|
||||
}
|
||||
function handleEditOrUpdateAction(e: Event) {
|
||||
const notificationType = initData.type;
|
||||
@@ -183,6 +184,12 @@ function initNotificationBar(message: NotificationBarWindowMessage) {
|
||||
notificationType === "add" ? sendSaveCipherMessage(true) : sendSaveCipherMessage(false);
|
||||
}
|
||||
}
|
||||
function handleCloseNotification(e: Event) {
|
||||
e.preventDefault();
|
||||
sendPlatformMessage({
|
||||
command: "bgCloseNotificationBar",
|
||||
});
|
||||
}
|
||||
|
||||
function handleSaveAction(e: Event) {
|
||||
e.preventDefault();
|
||||
@@ -282,6 +289,27 @@ function handleSaveCipherAttemptCompletedMessage(message: NotificationBarWindowM
|
||||
);
|
||||
}
|
||||
|
||||
function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) {
|
||||
const { theme, type } = notificationBarIframeInitData;
|
||||
const { error } = message;
|
||||
const i18n = getI18n();
|
||||
const resolvedTheme = getResolvedTheme(theme);
|
||||
|
||||
globalThis.setTimeout(() => sendPlatformMessage({ command: "bgCloseNotificationBar" }), 5000);
|
||||
|
||||
return render(
|
||||
NotificationConfirmationContainer({
|
||||
...notificationBarIframeInitData,
|
||||
type: type as NotificationType,
|
||||
theme: resolvedTheme,
|
||||
handleCloseNotification,
|
||||
i18n,
|
||||
error,
|
||||
}),
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
function handleTypeUnlock() {
|
||||
setContent(document.getElementById("template-unlock") as HTMLTemplateElement);
|
||||
|
||||
@@ -395,6 +423,14 @@ function getTheme(globalThis: any, theme: NotificationBarIframeInitData["theme"]
|
||||
return theme;
|
||||
}
|
||||
|
||||
function getResolvedTheme(theme: Theme) {
|
||||
const themeType = getTheme(globalThis, theme);
|
||||
|
||||
// There are other possible passed theme values, but for now, resolve to dark or light
|
||||
const resolvedTheme: Theme = themeType === ThemeTypes.Dark ? ThemeTypes.Dark : ThemeTypes.Light;
|
||||
return resolvedTheme;
|
||||
}
|
||||
|
||||
function setNotificationBarTheme() {
|
||||
const theme = getTheme(globalThis, notificationBarIframeInitData.theme);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user