Skip to content

Commit

Permalink
Butter bar alerts! 🧈 (#146)
Browse files Browse the repository at this point in the history
Here we:
- Add butter bar lit component 
- Add ButterBarService which manages alerts that should be shown in the
UI

New strings in[ this
PR](mozilla-l10n/mozilla-vpn-extension-l10n#2)


https://github.com/user-attachments/assets/a5517eaf-11ea-4a90-87ef-eb9ba416871b
  • Loading branch information
lesleyjanenorton authored Dec 13, 2024
1 parent 986d3b2 commit fbe06b3
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/assets/img/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
130 changes: 130 additions & 0 deletions src/background/butterBarService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @ts-check
import { Component } from "./component.js";
import { PropertyType } from "../shared/ipc.js";

import { IBindable, property } from "../shared/property.js";
import { VPNController } from "./vpncontroller/vpncontroller.js";
import { ConflictObserver } from "./conflictObserver.js";

/**
*
* ButterBarService manages 'Butter Bar' alerts shown
* in the UI.
*/

export class ButterBarService extends Component {
// Gets exposed to UI
static properties = {
butterBarList: PropertyType.Bindable,
removeAlert: PropertyType.Function,
};

/** @type {IBindable<Array<ButterBarAlert>>} */
// List of alerts passed to the UI
butterBarList = property([]);

/** @type {Array<String>} */
// List of alert IDs that have been dismissed
dismissedAlerts = [];

/**
*
* @param {*} receiver
* @param {VPNController} vpnController
* @param {ConflictObserver} conflictObserver
*/
constructor(receiver, vpnController, conflictObserver) {
super(receiver);
this.vpnController = vpnController;
this.conflictObserver = conflictObserver;
}

async init() {
console.log("Initializing ButterBarService");
await this.conflictObserver.updateList();

this.vpnController.interventions.subscribe((interventions) => {
const alert = new ButterBarAlert(
"conflictingProgram",
"alert_conflictingProgram",
"howToFix",
"https://support.mozilla.org/kb/program-your-computer-interferes-mozilla-vpn-exten?utm_medium=mozilla-vpn&utm_source=vpn-extension"
);
this.maybeCreateAlert(interventions, alert);
});

this.conflictObserver.conflictingAddons.subscribe((conflictingAddons) => {
const alert = new ButterBarAlert(
"alert_conflictingExtensions",
"alert_conflictingExtensions",
"learnWhatToDo",
"https://support.mozilla.org/kb/if-another-extension-interferes-mozilla-vpn?utm_medium=mozilla-vpn&utm_source=vpn-extension"
);

this.maybeCreateAlert(conflictingAddons, alert);
});
}
/**
* @param {Array} list
* @param {ButterBarAlert} alert
*/
maybeCreateAlert(list, alert) {
if (list.length == 0) {
return;
}
const { alertId } = alert;

if (
this.alertWasDismissed(alertId, this.dismissedAlerts) ||
this.alertInButterBarList(alertId, this.butterBarList.value)
) {
return;
}

return this.butterBarList.value.push(alert);
}

/**
* @param {string} id
* @param {Array} dismissedAlerts
*/
alertWasDismissed(id, dismissedAlerts) {
return dismissedAlerts.some((alertId) => alertId == id);
}

/**
* @param {string} id
* @param {Array} butterBarList
*/
alertInButterBarList(id, butterBarList) {
return butterBarList.some((alert) => alert.alertId == id);
}

removeAlert(id) {
const newAlertList = this.butterBarList.value.filter(
({ alertId }) => alertId !== id
);
this.dismissedAlerts.push(id);
this.butterBarList.set(newAlertList);
return;
}
}

export class ButterBarAlert {
/**
* @param {string} alertId
* @param {string} alertMessage
* @param {string} linkText
* @param {string} linkUrl
*/
constructor(alertId, alertMessage, linkText, linkUrl) {
(this.alertId = alertId),
(this.alertMessage = alertMessage),
(this.linkText = linkText),
(this.linkUrl = linkUrl);
}
}
6 changes: 5 additions & 1 deletion src/background/conflictObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export class ConflictObserver {
* @returns {boolean}
*/
static isConflicting(addon) {
return addon.enabled && addon.permissions.includes("proxy");
return (
addon.enabled &&
addon.permissions.includes("proxy") &&
addon.id !== "[email protected]"
);
}
}
8 changes: 8 additions & 0 deletions src/background/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { ExtensionController } from "./extensionController/index.js";
import { expose } from "../shared/ipc.js";
import { TabReloader } from "./tabReloader.js";
import { ConflictObserver } from "./conflictObserver.js";
import { ButterBarService } from "./butterBarService.js";

const log = Logger.logger("Main");

class Main {
Expand Down Expand Up @@ -43,6 +45,11 @@ class Main {
this.vpnController
);
tabReloader = new TabReloader(this, this.extController, this.proxyHandler);
butterBarService = new ButterBarService(
this,
this.vpnController,
this.conflictObserver
);

async init() {
log("Hello from the background script!");
Expand All @@ -54,6 +61,7 @@ class Main {
expose(this.extController);
expose(this.proxyHandler);
expose(this.conflictObserver);
expose(this.butterBarService);

this.#handlingEvent = false;
this.#processPendingEvents();
Expand Down
3 changes: 3 additions & 0 deletions src/background/tabHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export class TabHandler extends Component {
}

async maybeShowIcon() {
if (!this.siteContexts) {
return;
}
const currentTab = await Utils.getCurrentTab();
if (!currentTab) {
return;
Expand Down
130 changes: 130 additions & 0 deletions src/components/butter-bar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html, LitElement, css } from "../vendor/lit-all.min.js";
import { ghostButtonStyles } from "../components/styles.js";
import { tr } from "../shared/i18n.js";
import { butterBarService } from "../../ui/browserAction/backend.js";

export class ButterBar extends LitElement {
static properties = {
alertId: { type: String },
alertMessage: { type: String },
linkText: { type: String },
linkUrl: { type: String },
};

constructor() {
super();
this.alertId = "";
this.alertMessage = "";
this.linkText = "";
this.linkUrl = "";
}

render() {
const openLink = (url) => {
browser.tabs.create({ url });
window.close();
};

const removeAlert = (id) => {
butterBarService.removeAlert(id);
this.dispatchEvent(new CustomEvent("resize-popup", { bubbles: true }));
};

return html`
<div class="butter-bar">
<div class="butter-bar-text">
<p>
${tr(this.alertMessage)}<a
href=""
@click=${() => {
openLink(this.linkUrl);
}}
>${tr(this.linkText)}</a
>
</p>
</div>
<button
@click=${() => {
removeAlert(this.alertId);
}}
class="butter-bar-close ghost-btn"
>
<img aria-hidden="true" src="./../../assets/img/close.svg" />
</button>
</div>
`;
}

static styles = css`
${ghostButtonStyles}
.butter-bar {
margin-block-end: 16px;
background: var(--grey10);
border-radius: 4px;
display: flex;
flex-direction: row;
justify-content: space-between;
overflow: clip;
width: 320px;
word-wrap: anywhere;
box-sizing: border-box;
}
.butter-bar-text {
text-align: center;
flex: 1;
font-size: 13px;
padding: 8px 16px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
justify-items: center;
box-sizing: border-box;
width: 280px;
}
button.butter-bar-close.ghost-btn {
inline-size: 40px;
border: none;
}
button.butter-bar-close.ghost-btn::before {
border-radius: 0px;
}
a {
margin-inline-start: 5px;
font-family: "Inter Semi Bold";
color: inherit;
}
a:hover {
color: #000000;
opacity: 1;
transition: opacity 0.2s ease-in-out;
}
@media (prefers-color-scheme: dark) {
.butter-bar {
background: rgba(255, 255, 255, 0.02);
}
a:hover {
opacity: 0.7;
color: #ffffff;
}
a:active {
opacity: 0.5;
}
img {
filter: invert();
}
}
`;
}
customElements.define("butter-bar", ButterBar);
1 change: 1 addition & 0 deletions src/ui/browserAction/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ import { getExposedObject } from "../../shared/ipc.js";
export const vpnController = await getExposedObject("VPNController");
export const extController = await getExposedObject("ExtensionController");
export const proxyHandler = await getExposedObject("ProxyHandler");
export const butterBarService = await getExposedObject("ButterBarService");

export const ready = Promise.all([vpnController, proxyHandler]);
28 changes: 26 additions & 2 deletions src/ui/browserAction/popupPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import {
live,
} from "../../vendor/lit-all.min.js";

import { vpnController, proxyHandler, extController } from "./backend.js";
import {
vpnController,
proxyHandler,
extController,
butterBarService,
} from "./backend.js";

import { Utils } from "../../shared/utils.js";
import { tr } from "../../shared/i18n.js";
Expand All @@ -31,6 +36,7 @@ import "./../../components/vpncard.js";
import "./../../components/titlebar.js";
import "./../../components/iconbutton.js";
import "./../../components/mz-rings.js";
import "./../../components/butter-bar.js";
import { SiteContext } from "../../background/proxyHandler/siteContext.js";
import {
ServerCity,
Expand Down Expand Up @@ -58,6 +64,7 @@ export class BrowserActionPopup extends LitElement {
_siteContext: { type: Object },
hasSiteContext: { type: Boolean },
_siteContexts: { type: Array },
alerts: { type: Array },
};

constructor() {
Expand All @@ -74,10 +81,13 @@ export class BrowserActionPopup extends LitElement {
this._siteContexts = s;
});
extController.state.subscribe((s) => {
console.log(s);
this.extState = s;
this.updatePage();
});
butterBarService.butterBarList.subscribe((s) => {
this.alerts = s;
this.updatePage();
});
this.updatePage();
}
updatePage() {
Expand Down Expand Up @@ -172,6 +182,19 @@ export class BrowserActionPopup extends LitElement {
<stack-view ${ref(this.stackView)}>
<section data-title="Mozilla VPN">
<main>
<div class="butter-bar-holder">
${this.alerts.map(
(alert) => html`
<butter-bar
.alertId=${alert.alertId}
.alertMessage=${alert.alertMessage}
.linkText=${alert.linkText}
.linkUrl=${alert.linkUrl}
>
</butter-bar>
`
)}
</div>
<vpn-card
@toggle=${handleVPNToggle}
.enabled=${this.extState?.enabled}
Expand Down Expand Up @@ -477,6 +500,7 @@ export class BrowserActionPopup extends LitElement {
main {
padding: var(--padding-default) var(--padding-default) 0
var(--padding-default);
max-inline-size: var(--window-width);
}
.positioner.checkbox-positioner {
Expand Down
Loading

0 comments on commit fbe06b3

Please sign in to comment.