[PS-1693] feat(browser): implement theming for notification bar (#3805)

* feat(browser): implement theming for notification bar

* refactor(browser): split notification bar function

* refactor(browser): use own method for getCurrentTheme

* chore(browser): add close.svg file as an asset

this file is embedded in apps/browser/src/notification/bar.html

* feat(browser): change textContrast color on primary buttons

* feat(browser): use dedicated color variable for close button

* feat(browser): use textColor for close button

* feat(browser): implement styling for select fields

* feat(browser): improve close button styling, add hover effect
This commit is contained in:
Rafael Kraut 2022-10-27 19:34:47 +02:00 committed by GitHub
parent 0cb5ffd6ed
commit 2ffafa1f23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 255 additions and 100 deletions

View File

@ -7,6 +7,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
import { CipherType } from "@bitwarden/common/enums/cipherType";
import { PolicyType } from "@bitwarden/common/enums/policyType";
import { ThemeType } from "@bitwarden/common/enums/themeType";
import { Utils } from "@bitwarden/common/misc/utils";
import { CipherView } from "@bitwarden/common/models/view/cipher.view";
import { LoginUriView } from "@bitwarden/common/models/view/login-uri.view";
@ -125,13 +126,13 @@ export default class NotificationBackground {
}
if (tab != null) {
this.doNotificationQueueCheck(tab);
await this.doNotificationQueueCheck(tab);
return;
}
const currentTab = await BrowserApi.getTabFromCurrentWindow();
if (currentTab != null) {
this.doNotificationQueueCheck(currentTab);
await this.doNotificationQueueCheck(currentTab);
}
}
@ -144,7 +145,7 @@ export default class NotificationBackground {
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
}
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
private async doNotificationQueueCheck(tab: chrome.tabs.Tab): Promise<void> {
if (tab == null) {
return;
}
@ -167,6 +168,7 @@ export default class NotificationBackground {
type: "add",
typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
theme: await this.getCurrentTheme(),
},
});
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.ChangePassword) {
@ -174,6 +176,7 @@ export default class NotificationBackground {
type: "change",
typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
theme: await this.getCurrentTheme(),
},
});
}
@ -181,6 +184,18 @@ export default class NotificationBackground {
}
}
private async getCurrentTheme() {
const theme = await this.stateService.getTheme();
if (theme !== ThemeType.System) {
return theme;
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? ThemeType.Dark
: ThemeType.Light;
}
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
if (this.notificationQueue[i].tabId === tab.id) {

View File

@ -498,17 +498,13 @@ document.addEventListener("DOMContentLoaded", (event) => {
}
function closeExistingAndOpenBar(type: string, typeData: any) {
let barPage = "notification/bar.html";
switch (type) {
case "add":
barPage = barPage + "?add=1&isVaultLocked=" + typeData.isVaultLocked;
break;
case "change":
barPage = barPage + "?change=1&isVaultLocked=" + typeData.isVaultLocked;
break;
default:
break;
}
const barQueryParams = {
type,
isVaultLocked: typeData.isVaultLocked,
theme: typeData.theme,
};
const barQueryString = new URLSearchParams(barQueryParams).toString();
const barPage = "notification/bar.html?" + barQueryString;
const frame = document.getElementById("bit-notification-bar-iframe") as HTMLIFrameElement;
if (frame != null && frame.src.indexOf(barPage) >= 0) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xmlns:v="https://vecta.io/nano"><path d="M497.72 429.63l-169-174.82L498.11 82.24c6.956-7.168 6.956-18.85 0-26.018L449.934 6.31C446.585 2.859 442.076 1 437.31 1s-9.275 1.991-12.624 5.31l-168.62 172.04L87.196 6.45c-3.349-3.451-7.858-5.31-12.624-5.31s-9.275 1.991-12.624 5.31L13.9 56.362c-6.956 7.168-6.956 18.85 0 26.018l169.39 172.57L14.42 429.64c-3.349 3.451-5.281 8.097-5.281 13.009s1.803 9.558 5.281 13.009l48.176 49.912c3.478 3.584 7.987 5.442 12.624 5.442 4.508 0 9.146-1.726 12.624-5.442l168.23-174.16 168.36 174.03c3.478 3.584 7.986 5.442 12.624 5.442 4.509 0 9.146-1.726 12.624-5.442l48.176-49.912c3.349-3.451 5.281-8.097 5.281-13.009a19.32 19.32 0 0 0-5.41-12.876z"/></svg>

After

Width:  |  Height:  |  Size: 743 B

View File

@ -100,8 +100,7 @@
"web_accessible_resources": [
"notification/bar.html",
"images/icon38.png",
"images/icon38_locked.png",
"images/close.png"
"images/icon38_locked.png"
],
"applications": {
"gecko": {

View File

@ -106,12 +106,7 @@
},
"web_accessible_resources": [
{
"resources": [
"notification/bar.html",
"images/icon38.png",
"images/icon38_locked.png",
"images/close.png"
],
"resources": ["notification/bar.html", "images/icon38.png", "images/icon38_locked.png"],
"matches": ["<all_urls>"]
}
],

View File

@ -15,7 +15,16 @@
<div id="content"></div>
<div>
<button type="button" class="neutral" id="close-button">
<img id="close" alt="Close" />
<svg
id="close"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
xmlns:v="https://vecta.io/nano"
>
<path
d="M497.72 429.63l-169-174.82L498.11 82.24c6.956-7.168 6.956-18.85 0-26.018L449.934 6.31C446.585 2.859 442.076 1 437.31 1s-9.275 1.991-12.624 5.31l-168.62 172.04L87.196 6.45c-3.349-3.451-7.858-5.31-12.624-5.31s-9.275 1.991-12.624 5.31L13.9 56.362c-6.956 7.168-6.956 18.85 0 26.018l169.39 172.57L14.42 429.64c-3.349 3.451-5.281 8.097-5.281 13.009s1.803 9.558 5.281 13.009l48.176 49.912c3.478 3.584 7.987 5.442 12.624 5.442 4.508 0 9.146-1.726 12.624-5.442l168.23-174.16 168.36 174.03c3.478 3.584 7.986 5.442 12.624 5.442 4.509 0 9.146-1.726 12.624-5.442l48.176-49.912c3.349-3.451 5.281-8.097 5.281-13.009a19.32 19.32 0 0 0-5.41-12.876z"
/>
</svg>
</button>
</div>
</div>

View File

@ -2,6 +2,9 @@
require("./bar.scss");
document.addEventListener("DOMContentLoaded", () => {
const theme = getQueryVariable("theme");
document.documentElement.classList.add("theme_" + theme);
let i18n = {};
let lang = window.navigator.language;
@ -39,10 +42,6 @@ document.addEventListener("DOMContentLoaded", () => {
var changeButton = document.querySelector("#template-change .change-save");
changeButton.textContent = i18n.notificationChangeSave;
var closeIcon = document.getElementById("close");
closeIcon.src = chrome.runtime.getURL("images/close.png");
closeIcon.alt = i18n.close;
var closeButton = document.getElementById("close-button");
closeButton.title = i18n.close;
closeButton.setAttribute("aria-label", i18n.close);
@ -51,54 +50,10 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#template-change .change-text").textContent =
i18n.notificationChangeDesc;
if (getQueryVariable("add")) {
setContent(document.getElementById("template-add"));
var addButton = document.querySelector("#template-add-clone .add-save"), // eslint-disable-line
neverButton = document.querySelector("#template-add-clone .never-save"); // eslint-disable-line
addButton.addEventListener("click", (e) => {
e.preventDefault();
const folderId = document.querySelector("#template-add-clone .select-folder").value;
const bgAddSaveMessage = {
command: "bgAddSave",
folder: folderId,
};
sendPlatformMessage(bgAddSaveMessage);
});
neverButton.addEventListener("click", (e) => {
e.preventDefault();
sendPlatformMessage({
command: "bgNeverSave",
});
});
if (!isVaultLocked) {
const responseFoldersCommand = "notificationBarGetFoldersList";
chrome.runtime.onMessage.addListener((msg) => {
if (msg.command === responseFoldersCommand && msg.data) {
fillSelectorWithFolders(msg.data.folders);
}
});
sendPlatformMessage({
command: "bgGetDataForTab",
responseCommand: responseFoldersCommand,
});
}
} else if (getQueryVariable("change")) {
setContent(document.getElementById("template-change"));
var changeButton = document.querySelector("#template-change-clone .change-save"); // eslint-disable-line
changeButton.addEventListener("click", (e) => {
e.preventDefault();
const bgChangeSaveMessage = {
command: "bgChangeSave",
};
sendPlatformMessage(bgChangeSaveMessage);
});
if (getQueryVariable("type") === "add") {
handleTypeAdd(isVaultLocked);
} else if (getQueryVariable("type") === "change") {
handleTypeChange();
}
closeButton.addEventListener("click", (e) => {
@ -126,6 +81,58 @@ document.addEventListener("DOMContentLoaded", () => {
return null;
}
function handleTypeAdd(isVaultLocked) {
setContent(document.getElementById("template-add"));
var addButton = document.querySelector("#template-add-clone .add-save"), // eslint-disable-line
neverButton = document.querySelector("#template-add-clone .never-save"); // eslint-disable-line
addButton.addEventListener("click", (e) => {
e.preventDefault();
const folderId = document.querySelector("#template-add-clone .select-folder").value;
const bgAddSaveMessage = {
command: "bgAddSave",
folder: folderId,
};
sendPlatformMessage(bgAddSaveMessage);
});
neverButton.addEventListener("click", (e) => {
e.preventDefault();
sendPlatformMessage({
command: "bgNeverSave",
});
});
if (!isVaultLocked) {
const responseFoldersCommand = "notificationBarGetFoldersList";
chrome.runtime.onMessage.addListener((msg) => {
if (msg.command === responseFoldersCommand && msg.data) {
fillSelectorWithFolders(msg.data.folders);
}
});
sendPlatformMessage({
command: "bgGetDataForTab",
responseCommand: responseFoldersCommand,
});
}
}
function handleTypeChange() {
setContent(document.getElementById("template-change"));
var changeButton = document.querySelector("#template-change-clone .change-save"); // eslint-disable-line
changeButton.addEventListener("click", (e) => {
e.preventDefault();
const bgChangeSaveMessage = {
command: "bgChangeSave",
};
sendPlatformMessage(bgChangeSaveMessage);
});
}
function setContent(element) {
const content = document.getElementById("content");
while (content.firstChild) {

View File

@ -1,22 +1,31 @@
body {
background-color: #ffffff;
@import "variables.scss";
body {
padding: 0;
margin: 0;
height: 100%;
font-size: 14px;
line-height: 16px;
color: #333333;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: $font-family-sans-serif;
@include themify($themes) {
color: themed("textColor");
background-color: themed("backgroundColor");
}
}
.outer-wrapper {
padding: 0 10px;
border-bottom: 2px solid #175ddc;
border-bottom: 2px solid transparent;
display: grid;
grid-template-columns: 24px auto 55px;
grid-column-gap: 10px;
box-sizing: border-box;
min-height: 42px;
@include themify($themes) {
border-bottom-color: themed("primaryColor");
}
}
.inner-wrapper {
@ -41,42 +50,86 @@ img {
display: block;
}
#close {
width: 15px;
height: 15px;
display: block;
padding: 5px 0;
#close-button {
margin-right: 10px;
}
button:not(.link),
button:not(.neutral) {
background-color: #175ddc;
padding: 5px 15px;
border-radius: 3px;
color: #ffffff;
border: 0;
#close {
display: block;
width: 17px;
height: 17px;
> path {
@include themify($themes) {
fill: themed("textColor");
}
}
}
#close-button:hover {
@include themify($themes) {
border-color: rgba(themed("textColor"), 0.2);
background-color: rgba(themed("textColor"), 0.2);
}
}
button {
padding: 0.35rem 15px;
border-radius: $border-radius;
border: 1px solid transparent;
cursor: pointer;
}
button:not(.neutral):not(.link) {
@include themify($themes) {
background-color: themed("primaryColor");
color: themed("textContrast");
border-color: themed("primaryColor");
}
&:hover {
cursor: pointer;
background-color: #1751bd;
@include themify($themes) {
background-color: darken(themed("primaryColor"), 1.5%);
color: darken(themed("textContrast"), 6%);
}
}
}
button.link,
button.neutral {
background: none;
padding: 5px 15px;
color: #175ddc;
border: 0;
@include themify($themes) {
background-color: transparent;
color: themed("primaryColor");
}
&:hover {
cursor: pointer;
background: none;
text-decoration: underline;
@include themify($themes) {
background-color: transparent;
color: darken(themed("primaryColor"), 6%);
}
}
}
select {
padding: 0.35rem;
border: 1px solid #000000;
border-radius: $border-radius;
@include themify($themes) {
color: themed("textColor");
background-color: themed("inputBackgroundColor");
border-color: themed("inputBorderColor");
}
}
select,
button {
font-size: $font-size-base;
font-family: $font-family-sans-serif;
}
.select-folder[isVaultLocked="true"] {
display: none;
}

View File

@ -0,0 +1,80 @@
@import "~nord/src/sass/nord.scss";
$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
$font-size-base: 14px;
$text-color: #333333;
$border-color-dark: #ddd;
$border-radius: 3px;
$brand-primary: #175ddc;
$background-color: #f0f0f0;
$solarizedDarkBase03: #002b36;
$solarizedDarkBase02: #073642;
$solarizedDarkBase01: #586e75;
$solarizedDarkBase2: #eee8d5;
$solarizedDarkCyan: #2aa198;
$solarizedDarkGreen: #859900;
$themes: (
light: (
textColor: $text-color,
backgroundColor: $background-color,
primaryColor: $brand-primary,
buttonPrimaryColor: $brand-primary,
textContrast: $background-color,
inputBorderColor: darken($border-color-dark, 7%),
inputBackgroundColor: #ffffff,
),
dark: (
textColor: #ffffff,
backgroundColor: #2f343d,
buttonPrimaryColor: #6f9df1,
primaryColor: #6f9df1,
textContrast: #2f343d,
inputBorderColor: #4c525f,
inputBackgroundColor: #2f343d,
),
nord: (
textColor: $nord5,
backgroundColor: $nord1,
buttonPrimaryColor: $nord8,
primaryColor: $nord9,
textContrast: $nord2,
inputBorderColor: $nord0,
inputBackgroundColor: $nord2,
),
solarizedDark: (
textColor: $solarizedDarkBase2,
backgroundColor: $solarizedDarkBase03,
buttonPrimaryColor: $solarizedDarkCyan,
primaryColor: $solarizedDarkGreen,
textContrast: $solarizedDarkBase02,
inputBorderColor: rgba($solarizedDarkBase2, 0.2),
inputBackgroundColor: $solarizedDarkBase01,
),
);
@mixin themify($themes: $themes) {
@each $theme, $map in $themes {
html.theme_#{$theme} & {
$theme-map: () !global;
@each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), "#{$key}");
$theme-map: map-merge(
$theme-map,
(
$key: $value,
)
) !global;
}
@content;
$theme-map: null !global;
}
}
}
@function themed($key) {
@return map-get($theme-map, $key);
}