Bug 1964835 - Part 4: Remove shopping browser component. r=shopping-reviewers,desktop-theme-reviewers,frontend-codestyle-reviewers,emilio,kpatenio
Differential Revision: https://phabricator.services.mozilla.com/D248334
@@ -442,7 +442,6 @@ const rollouts = [
|
||||
"browser/components/sidebar/**",
|
||||
"browser/components/shell/**",
|
||||
"browser/components/sessionstore/**",
|
||||
"browser/components/shopping/**",
|
||||
"browser/components/storybook/.storybook/**",
|
||||
"browser/components/storybook/custom-elements-manifest.config.mjs",
|
||||
"browser/components/syncedtabs/**",
|
||||
|
||||
@@ -60,7 +60,6 @@ DIRS += [
|
||||
"search",
|
||||
"sessionstore",
|
||||
"shell",
|
||||
"shopping",
|
||||
"sidebar",
|
||||
"syncedtabs",
|
||||
"tabbrowser",
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
||||
ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs",
|
||||
});
|
||||
|
||||
const ABOUT_SHOPPING_SIDEBAR = "about:shoppingsidebar";
|
||||
const ABOUT_BLANK = "about:blank";
|
||||
|
||||
/**
|
||||
* When a Review Checker sidebar panel is open ReviewCheckerParent
|
||||
* handles listening for location changes and determining if the
|
||||
* location or the current URI is a product or not.
|
||||
*
|
||||
* The ReviewCheckerChild will use that info to update the sidebar UI.
|
||||
*
|
||||
* This is a simplified version of the `ShoppingSidebarParent` for
|
||||
* using the shopping components in the main sidebar instead of the
|
||||
* custom shopping sidebar.
|
||||
*/
|
||||
export class ReviewCheckerParent extends JSWindowActorParent {
|
||||
static SHOPPING_OPTED_IN_PREF = "browser.shopping.experience2023.optedIn";
|
||||
static CLOSE_SIDEBAR = "CloseReviewCheckerSidebar";
|
||||
static REVERSE_SIDEBAR = "ReverseSidebarPositionFromReviewChecker";
|
||||
static SHOW_SIDEBAR_SETTINGS = "ShowSidebarSettingsFromReviewChecker";
|
||||
static DISPATCH_NEW_POSITION_CARD_IF_ELIGIBLE =
|
||||
"DispatchNewPositionCardIfEligible";
|
||||
|
||||
actorCreated() {
|
||||
this.topBrowserWindow = this.browsingContext.topChromeWindow;
|
||||
this.topBrowserWindow.gBrowser.addProgressListener(this);
|
||||
|
||||
/* The manager class that tracks auto-open behaviour is likely to be instantiated before ReviewCheckerParent.
|
||||
* Once the parent actor is created, dispatch an event to the manager to see if we
|
||||
* can render the notification for the RC's new sidebar position. Also be sure to set up an
|
||||
* eventListener so that we can hear back from the manager. */
|
||||
this.showNewPositionCard = this.showNewPositionCard.bind(this);
|
||||
this.topBrowserWindow.addEventListener(
|
||||
"ReviewCheckerManager:ShowNewPositionCard",
|
||||
this.showNewPositionCard
|
||||
);
|
||||
this.dispatchNewPositionCardIfEligible();
|
||||
}
|
||||
|
||||
didDestroy() {
|
||||
if (!this.topBrowserWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.topBrowserWindow.removeEventListener(
|
||||
"ReviewCheckerManager:ShowNewPositionCard",
|
||||
this.showNewPositionCard
|
||||
);
|
||||
this.topBrowserWindow.gBrowser.removeProgressListener(this);
|
||||
this.topBrowserWindow = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a URL is ignored by ReviewChecker.
|
||||
*
|
||||
* @param {string} url the url that we want to validate.
|
||||
*/
|
||||
static isIgnoredURL(url) {
|
||||
// about:shoppingsidebar is only used for testing with fake data.
|
||||
return url == ABOUT_SHOPPING_SIDEBAR || url == ABOUT_BLANK;
|
||||
}
|
||||
|
||||
updateCurrentURL(uri, flags, isSimulated) {
|
||||
if (ReviewCheckerParent.isIgnoredURL(uri.spec)) {
|
||||
uri = null;
|
||||
}
|
||||
this.sendAsyncMessage("ReviewChecker:UpdateCurrentURL", {
|
||||
url: uri?.spec,
|
||||
isReload: !!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD),
|
||||
isSimulated,
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentURL() {
|
||||
let { selectedBrowser } = this.topBrowserWindow.gBrowser;
|
||||
let uri = selectedBrowser.currentURI;
|
||||
|
||||
// Remove the unhandled navigation flag if present, as the
|
||||
// product will be handled now.
|
||||
lazy.ShoppingUtils.clearIsDistinctProductPageVisitFlag(selectedBrowser);
|
||||
|
||||
if (ReviewCheckerParent.isIgnoredURL(uri.spec)) {
|
||||
return null;
|
||||
}
|
||||
return uri?.spec;
|
||||
}
|
||||
|
||||
showNewPositionCard() {
|
||||
this.sendAsyncMessage("ReviewChecker:ShowNewPositionCard");
|
||||
}
|
||||
|
||||
async receiveMessage(message) {
|
||||
if (this.browsingContext.usePrivateBrowsing) {
|
||||
throw new Error("We should never be invoked in PBM.");
|
||||
}
|
||||
switch (message.name) {
|
||||
case "GetCurrentURL":
|
||||
return this.getCurrentURL();
|
||||
case "DisableShopping":
|
||||
Services.prefs.setIntPref(
|
||||
ReviewCheckerParent.SHOPPING_OPTED_IN_PREF,
|
||||
2
|
||||
);
|
||||
this.closeSidebarPanel();
|
||||
break;
|
||||
case "CloseShoppingSidebar":
|
||||
this.closeSidebarPanel();
|
||||
break;
|
||||
case "ReverseSidebarPosition":
|
||||
this.reverseSidebarPosition();
|
||||
break;
|
||||
case "ShowSidebarSettings":
|
||||
this.showSidebarSettings();
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by TabsProgressListener whenever any browser navigates from one
|
||||
* URL to another.
|
||||
* Note that this includes hash changes / pushState navigations, because
|
||||
* those can be significant for us.
|
||||
*/
|
||||
onLocationChange(
|
||||
aWebProgress,
|
||||
_aRequest,
|
||||
aLocationURI,
|
||||
aFlags,
|
||||
aIsSimulated
|
||||
) {
|
||||
if (aWebProgress && !aWebProgress.isTopLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(
|
||||
this.topBrowserWindow
|
||||
);
|
||||
if (isPBM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this tab changed to a new location in the background
|
||||
// it will have an `isDistinctProductPageVisit` flag.
|
||||
// If that is present we need to handle the navigation as
|
||||
// an un-simulated location change.
|
||||
let { selectedBrowser } = this.topBrowserWindow.gBrowser;
|
||||
if (selectedBrowser.isDistinctProductPageVisit) {
|
||||
aIsSimulated = false;
|
||||
lazy.ShoppingUtils.clearIsDistinctProductPageVisitFlag(selectedBrowser);
|
||||
}
|
||||
|
||||
this.updateCurrentURL(aLocationURI, aFlags, aIsSimulated);
|
||||
}
|
||||
|
||||
dispatchEvent(eventName) {
|
||||
let event = new CustomEvent(eventName, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
this.topBrowserWindow.dispatchEvent(event);
|
||||
}
|
||||
|
||||
closeSidebarPanel() {
|
||||
this.dispatchEvent(ReviewCheckerParent.CLOSE_SIDEBAR);
|
||||
}
|
||||
|
||||
dispatchNewPositionCardIfEligible() {
|
||||
this.dispatchEvent(
|
||||
ReviewCheckerParent.DISPATCH_NEW_POSITION_CARD_IF_ELIGIBLE
|
||||
);
|
||||
}
|
||||
|
||||
reverseSidebarPosition() {
|
||||
this.dispatchEvent(ReviewCheckerParent.REVERSE_SIDEBAR);
|
||||
}
|
||||
|
||||
showSidebarSettings() {
|
||||
this.dispatchEvent(ReviewCheckerParent.SHOW_SIDEBAR_SETTINGS);
|
||||
}
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";
|
||||
|
||||
import { ShoppingProduct } from "chrome://global/content/shopping/ShoppingProduct.mjs";
|
||||
|
||||
let lazy = {};
|
||||
|
||||
let gAllActors = new Set();
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"optedIn",
|
||||
"browser.shopping.experience2023.optedIn",
|
||||
null,
|
||||
function optedInStateChanged() {
|
||||
for (let actor of gAllActors) {
|
||||
actor.optedInStateChanged();
|
||||
}
|
||||
}
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"adsEnabled",
|
||||
"browser.shopping.experience2023.ads.enabled",
|
||||
true
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"adsEnabledByUser",
|
||||
"browser.shopping.experience2023.ads.userEnabled",
|
||||
true,
|
||||
function adsEnabledByUserChanged() {
|
||||
for (let actor of gAllActors) {
|
||||
actor.adsEnabledByUserChanged();
|
||||
}
|
||||
}
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"autoOpenEnabled",
|
||||
"browser.shopping.experience2023.autoOpen.enabled",
|
||||
true
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"autoOpenEnabledByUser",
|
||||
"browser.shopping.experience2023.autoOpen.userEnabled",
|
||||
true,
|
||||
function autoOpenEnabledByUserChanged() {
|
||||
for (let actor of gAllActors) {
|
||||
actor.autoOpenEnabledByUserChanged();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export class ShoppingSidebarChild extends RemotePageChild {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
actorCreated() {
|
||||
super.actorCreated();
|
||||
gAllActors.add(this);
|
||||
}
|
||||
|
||||
didDestroy() {
|
||||
this._destroyed = true;
|
||||
super.didDestroy?.();
|
||||
gAllActors.delete(this);
|
||||
this.#product?.off("analysis-progress", this.#onAnalysisProgress);
|
||||
this.#product?.uninit();
|
||||
}
|
||||
|
||||
#productURI = null;
|
||||
#product = null;
|
||||
|
||||
receiveMessage(message) {
|
||||
if (this.browsingContext.usePrivateBrowsing) {
|
||||
throw new Error("We should never be invoked in PBM.");
|
||||
}
|
||||
switch (message.name) {
|
||||
case "ShoppingSidebar:UpdateProductURL":
|
||||
this.handleURLUpdate(message.data);
|
||||
break;
|
||||
case "ShoppingSidebar:ShowKeepClosedMessage":
|
||||
this.sendToContent("ShowKeepClosedMessage");
|
||||
break;
|
||||
case "ShoppingSidebar:HideKeepClosedMessage":
|
||||
this.sendToContent("HideKeepClosedMessage");
|
||||
break;
|
||||
case "ShoppingSidebar:IsKeepClosedMessageShowing":
|
||||
return !!this.document.querySelector("shopping-container")
|
||||
?.wrappedJSObject.showingKeepClosedMessage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
handleURLUpdate(data) {
|
||||
let { url, isReload } = data;
|
||||
let uri = url ? Services.io.newURI(url) : null;
|
||||
|
||||
// If we're going from null to null, bail out:
|
||||
if (!this.#productURI && !uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we haven't reloaded, check if the URIs represent the same product
|
||||
// as sites might change the URI after they have loaded (Bug 1852099).
|
||||
if (!isReload && this.isSameProduct(uri, this.#productURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#productURI = uri;
|
||||
this.updateContent({ haveUpdatedURI: true });
|
||||
}
|
||||
|
||||
isSameProduct(newURI, currentURI) {
|
||||
if (!newURI || !currentURI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the URIs are equal:
|
||||
if (currentURI.equalsExceptRef(newURI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.#product) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the current ShoppingProduct has product info set,
|
||||
// check if the product ids are the same:
|
||||
let currentProduct = this.#product.product;
|
||||
if (currentProduct) {
|
||||
let newProduct = ShoppingProduct.fromURL(URL.fromURI(newURI));
|
||||
if (newProduct.id === currentProduct.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
let aid, sponsored;
|
||||
switch (event.type) {
|
||||
case "ContentReady":
|
||||
this.updateContent();
|
||||
break;
|
||||
case "PolledRequestMade":
|
||||
this.updateContent({ isPolledRequest: true });
|
||||
break;
|
||||
case "ReportProductAvailable":
|
||||
this.reportProductAvailable();
|
||||
break;
|
||||
case "AdClicked":
|
||||
aid = event.detail.aid;
|
||||
sponsored = event.detail.sponsored;
|
||||
ShoppingProduct.sendAttributionEvent("click", aid);
|
||||
Glean.shopping.surfaceAdsClicked.record({ sponsored });
|
||||
break;
|
||||
case "AdImpression":
|
||||
aid = event.detail.aid;
|
||||
sponsored = event.detail.sponsored;
|
||||
ShoppingProduct.sendAttributionEvent("impression", aid);
|
||||
Glean.shopping.surfaceAdsImpression.record({ sponsored });
|
||||
break;
|
||||
case "DisableShopping":
|
||||
this.sendAsyncMessage("DisableShopping");
|
||||
break;
|
||||
case "CloseShoppingSidebar":
|
||||
this.sendAsyncMessage("CloseShoppingSidebar");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Exposed for testing. Assumes uri is a nsURI.
|
||||
set productURI(uri) {
|
||||
if (!(uri instanceof Ci.nsIURI)) {
|
||||
throw new Error("productURI setter expects an nsIURI");
|
||||
}
|
||||
this.#productURI = uri;
|
||||
}
|
||||
|
||||
// Exposed for testing. Assumes product is a ShoppingProduct.
|
||||
set product(product) {
|
||||
if (!(product instanceof ShoppingProduct)) {
|
||||
throw new Error("product setter expects an instance of ShoppingProduct");
|
||||
}
|
||||
this.#product = product;
|
||||
}
|
||||
|
||||
get canFetchAndShowData() {
|
||||
return lazy.optedIn === 1;
|
||||
}
|
||||
|
||||
get adsEnabled() {
|
||||
return lazy.adsEnabled;
|
||||
}
|
||||
|
||||
get adsEnabledByUser() {
|
||||
return lazy.adsEnabledByUser;
|
||||
}
|
||||
|
||||
get canFetchAndShowAd() {
|
||||
return this.adsEnabled && this.adsEnabledByUser;
|
||||
}
|
||||
|
||||
get autoOpenEnabled() {
|
||||
return lazy.autoOpenEnabled;
|
||||
}
|
||||
|
||||
get autoOpenEnabledByUser() {
|
||||
return lazy.autoOpenEnabledByUser;
|
||||
}
|
||||
|
||||
optedInStateChanged() {
|
||||
// Force re-fetching things if needed by clearing the last product URI:
|
||||
this.#productURI = null;
|
||||
// Then let content know.
|
||||
this.updateContent({ focusCloseButton: true });
|
||||
}
|
||||
|
||||
adsEnabledByUserChanged() {
|
||||
this.sendToContent("adsEnabledByUserChanged", {
|
||||
adsEnabledByUser: this.adsEnabledByUser,
|
||||
});
|
||||
|
||||
this.requestRecommendations(this.#productURI);
|
||||
}
|
||||
|
||||
autoOpenEnabledByUserChanged() {
|
||||
this.sendToContent("autoOpenEnabledByUserChanged", {
|
||||
autoOpenEnabledByUser: this.autoOpenEnabledByUser,
|
||||
});
|
||||
}
|
||||
|
||||
getProductURI() {
|
||||
return this.#productURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback is invoked whenever something changes that requires
|
||||
* re-rendering content. The expected cases for this are:
|
||||
* - page navigations (both to new products and away from a product once
|
||||
* the sidebar has been created)
|
||||
* - opt in state changes.
|
||||
*
|
||||
* @param {object?} options
|
||||
* Optional parameter object.
|
||||
* @param {bool} options.haveUpdatedURI = false
|
||||
* Whether we've got an up-to-date URI already. If true, we avoid
|
||||
* fetching the URI from the parent, and assume `this.#productURI`
|
||||
* is current. Defaults to false.
|
||||
* @param {bool} options.isPolledRequest = false
|
||||
* @param {bool} options.focusCloseButton = false
|
||||
*/
|
||||
async updateContent({
|
||||
haveUpdatedURI = false,
|
||||
isPolledRequest = false,
|
||||
focusCloseButton = false,
|
||||
} = {}) {
|
||||
// updateContent is an async function, and when we're off making requests or doing
|
||||
// other things asynchronously, the actor can be destroyed, the user
|
||||
// might navigate to a new page, the user might disable the feature ... -
|
||||
// all kinds of things can change. So we need to repeatedly check
|
||||
// whether we can keep going with our async processes. This helper takes
|
||||
// care of these checks.
|
||||
let canContinue = (currentURI, checkURI = true) => {
|
||||
if (this._destroyed || !this.canFetchAndShowData) {
|
||||
return false;
|
||||
}
|
||||
if (!checkURI) {
|
||||
return true;
|
||||
}
|
||||
return currentURI && currentURI == this.#productURI;
|
||||
};
|
||||
this.#product?.off("analysis-progress", this.#onAnalysisProgress);
|
||||
this.#product?.uninit();
|
||||
// We are called either because the URL has changed or because the opt-in
|
||||
// state has changed. In both cases, we want to clear out content
|
||||
// immediately, without waiting for potentially async operations like
|
||||
// obtaining product information.
|
||||
// Do not clear data however if an analysis was requested via a call-to-action.
|
||||
if (!isPolledRequest) {
|
||||
this.sendToContent("Update", {
|
||||
adsEnabled: this.adsEnabled,
|
||||
adsEnabledByUser: this.adsEnabledByUser,
|
||||
autoOpenEnabled: this.autoOpenEnabled,
|
||||
autoOpenEnabledByUser: this.autoOpenEnabledByUser,
|
||||
showOnboarding: !this.canFetchAndShowData,
|
||||
data: null,
|
||||
recommendationData: null,
|
||||
focusCloseButton,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.canFetchAndShowData) {
|
||||
if (!this.#productURI) {
|
||||
// If we already have a URI and it's just null, bail immediately.
|
||||
if (haveUpdatedURI) {
|
||||
return;
|
||||
}
|
||||
let url = await this.sendQuery("GetProductURL");
|
||||
|
||||
// Bail out if we opted out in the meantime, or don't have a URI.
|
||||
if (!canContinue(null, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#productURI = url ? Services.io.newURI(url) : null;
|
||||
}
|
||||
|
||||
let uri = this.#productURI;
|
||||
this.#product = new ShoppingProduct(uri);
|
||||
this.#product.on(
|
||||
"analysis-progress",
|
||||
this.#onAnalysisProgress.bind(this)
|
||||
);
|
||||
|
||||
let data;
|
||||
let isAnalysisInProgress;
|
||||
|
||||
try {
|
||||
let analysisStatusResponse;
|
||||
if (isPolledRequest) {
|
||||
// Request a new analysis.
|
||||
analysisStatusResponse = await this.#product.requestCreateAnalysis();
|
||||
} else {
|
||||
// Check if there is an analysis in progress.
|
||||
analysisStatusResponse =
|
||||
await this.#product.requestAnalysisCreationStatus();
|
||||
}
|
||||
let analysisStatus = analysisStatusResponse?.status;
|
||||
|
||||
isAnalysisInProgress =
|
||||
analysisStatus &&
|
||||
(analysisStatus == "pending" || analysisStatus == "in_progress");
|
||||
if (isAnalysisInProgress) {
|
||||
// Only clear the existing data if the update wasn't
|
||||
// triggered by a Polled Request event as re-analysis should
|
||||
// keep any stale data visible while processing.
|
||||
if (!isPolledRequest) {
|
||||
this.sendToContent("Update", {
|
||||
isAnalysisInProgress,
|
||||
});
|
||||
}
|
||||
analysisStatusResponse = await this.#product.pollForAnalysisCompleted(
|
||||
{
|
||||
pollInitialWait: analysisStatus == "in_progress" ? 0 : undefined,
|
||||
}
|
||||
);
|
||||
analysisStatus = analysisStatusResponse?.status;
|
||||
isAnalysisInProgress = false;
|
||||
}
|
||||
|
||||
// Use the analysis status instead of re-requesting unnecessarily,
|
||||
// or throw if the status from the last analysis was an error.
|
||||
switch (analysisStatus) {
|
||||
case "not_analyzable":
|
||||
case "page_not_supported":
|
||||
data = { page_not_supported: true };
|
||||
break;
|
||||
case "not_enough_reviews":
|
||||
data = { not_enough_reviews: true };
|
||||
break;
|
||||
case "unprocessable":
|
||||
case "stale":
|
||||
throw new Error(analysisStatus, { cause: analysisStatus });
|
||||
default:
|
||||
// Status is "completed" or "not_found" (no analysis status),
|
||||
// so we should request the analysis data.
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
data = await this.#product.requestAnalysis();
|
||||
if (!data) {
|
||||
throw new Error("request failed");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch product analysis data", err);
|
||||
data = { error: err };
|
||||
}
|
||||
// Check if we got nuked from orbit, or the product URI or opt in changed while we waited.
|
||||
if (!canContinue(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendToContent("Update", {
|
||||
showOnboarding: false,
|
||||
data,
|
||||
productUrl: this.#productURI.spec,
|
||||
isAnalysisInProgress,
|
||||
});
|
||||
|
||||
if (!data || data.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPolledRequest && !data.grade) {
|
||||
Glean.shopping.surfaceNoReviewReliabilityAvailable.record();
|
||||
}
|
||||
|
||||
this.requestRecommendations(uri);
|
||||
} else {
|
||||
// Don't bother continuing if the user has opted out.
|
||||
if (lazy.optedIn == 2) {
|
||||
return;
|
||||
}
|
||||
let url = await this.sendQuery("GetProductURL");
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Similar to canContinue() above, check to see if things
|
||||
// have changed while we were waiting. Bail out if the user
|
||||
// opted in, or if the actor doesn't exist.
|
||||
if (this._destroyed || this.canFetchAndShowData) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#productURI = url ? Services.io.newURI(url) : null;
|
||||
|
||||
// Send the productURI to content for Onboarding's dynamic text
|
||||
this.sendToContent("Update", {
|
||||
showOnboarding: true,
|
||||
data: null,
|
||||
productUrl: this.#productURI?.spec,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to determine if we should request ads.
|
||||
*/
|
||||
canFetchAds(uri) {
|
||||
return (
|
||||
uri.equalsExceptRef(this.#productURI) &&
|
||||
this.canFetchAndShowData &&
|
||||
this.canFetchAndShowAd
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to determine if we should display ads. This is different
|
||||
* from fetching ads, because of ads exposure telemetry (bug 1858470).
|
||||
*/
|
||||
canShowAds(uri) {
|
||||
return (
|
||||
uri.equalsExceptRef(this.#productURI) &&
|
||||
this.canFetchAndShowData &&
|
||||
this.canFetchAndShowAd
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request recommended products for a given uri and send the recommendations
|
||||
* to the content if recommendations are enabled.
|
||||
*
|
||||
* @param {nsIURI} uri The uri of the current product page
|
||||
*/
|
||||
async requestRecommendations(uri) {
|
||||
if (!this.canFetchAds(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let recommendationData = await this.#product.requestRecommendations();
|
||||
|
||||
// Check if the product URI or opt in changed while we waited.
|
||||
if (!this.canShowAds(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!recommendationData.length) {
|
||||
// We tried to fetch an ad, but didn't get one.
|
||||
Glean.shopping.surfaceNoAdsAvailable.record();
|
||||
} else {
|
||||
let sponsored = recommendationData[0].sponsored;
|
||||
|
||||
ShoppingProduct.sendAttributionEvent(
|
||||
"placement",
|
||||
recommendationData[0].aid
|
||||
);
|
||||
|
||||
Glean.shopping.surfaceAdsPlacement.record({
|
||||
sponsored,
|
||||
});
|
||||
}
|
||||
|
||||
this.sendToContent("UpdateRecommendations", {
|
||||
recommendationData,
|
||||
});
|
||||
}
|
||||
|
||||
sendToContent(eventName, detail) {
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
let win = this.contentWindow;
|
||||
let evt = new win.CustomEvent(eventName, {
|
||||
bubbles: true,
|
||||
detail: Cu.cloneInto(detail, win),
|
||||
});
|
||||
win.document.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
async reportProductAvailable() {
|
||||
await this.#product.sendReport();
|
||||
}
|
||||
|
||||
#onAnalysisProgress(eventName, progress) {
|
||||
this.sendToContent("UpdateAnalysisProgress", {
|
||||
progress,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
||||
EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
|
||||
isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
||||
ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs",
|
||||
});
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"AUTO_OPEN_SIDEBAR_ENABLED",
|
||||
"browser.shopping.experience2023.autoOpen.enabled",
|
||||
true
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"AUTO_OPEN_SIDEBAR_USER_ENABLED",
|
||||
"browser.shopping.experience2023.autoOpen.userEnabled",
|
||||
true
|
||||
);
|
||||
|
||||
export class ShoppingSidebarParent extends JSWindowActorParent {
|
||||
static SHOPPING_ENABLED_PREF = "browser.shopping.experience2023.enabled";
|
||||
static SHOPPING_ACTIVE_PREF = "browser.shopping.experience2023.active";
|
||||
static SHOPPING_OPTED_IN_PREF = "browser.shopping.experience2023.optedIn";
|
||||
static SIDEBAR_CLOSED_COUNT_PREF =
|
||||
"browser.shopping.experience2023.sidebarClosedCount";
|
||||
static SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
|
||||
"browser.shopping.experience2023.showKeepSidebarClosedMessage";
|
||||
|
||||
updateCurrentURL(uri, flags) {
|
||||
this.sendAsyncMessage("ShoppingSidebar:UpdateProductURL", {
|
||||
url: uri?.spec ?? null,
|
||||
isReload: !!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD),
|
||||
});
|
||||
}
|
||||
|
||||
async receiveMessage(message) {
|
||||
if (this.browsingContext.usePrivateBrowsing) {
|
||||
throw new Error("We should never be invoked in PBM.");
|
||||
}
|
||||
switch (message.name) {
|
||||
case "GetProductURL":
|
||||
return this.getCurrentURL();
|
||||
case "DisableShopping":
|
||||
Services.prefs.setBoolPref(
|
||||
ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
|
||||
false
|
||||
);
|
||||
Services.prefs.setIntPref(
|
||||
ShoppingSidebarParent.SHOPPING_OPTED_IN_PREF,
|
||||
2
|
||||
);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of the current tab.
|
||||
*/
|
||||
getCurrentURL() {
|
||||
let sidebarBrowser = this.browsingContext.top.embedderElement;
|
||||
let panel = sidebarBrowser.closest(".browserSidebarContainer");
|
||||
let associatedTabbedBrowser = panel.querySelector(
|
||||
"browser[messagemanagergroup=browsers]"
|
||||
);
|
||||
return associatedTabbedBrowser.currentURI?.spec ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the URL bar button.
|
||||
*/
|
||||
static async urlbarButtonClick(event) {
|
||||
if (
|
||||
lazy.AUTO_OPEN_SIDEBAR_ENABLED &&
|
||||
lazy.AUTO_OPEN_SIDEBAR_USER_ENABLED &&
|
||||
event.target.getAttribute("shoppingsidebaropen") === "true"
|
||||
) {
|
||||
let gBrowser = event.target.ownerGlobal.gBrowser;
|
||||
let shoppingBrowser = gBrowser
|
||||
.getPanel(gBrowser.selectedBrowser)
|
||||
.querySelector(".shopping-sidebar");
|
||||
let actor =
|
||||
shoppingBrowser.browsingContext.currentWindowGlobal.getActor(
|
||||
"ShoppingSidebar"
|
||||
);
|
||||
|
||||
let isKeepClosedMessageShowing = await actor.sendQuery(
|
||||
"ShoppingSidebar:IsKeepClosedMessageShowing"
|
||||
);
|
||||
|
||||
let sidebarClosedCount = Services.prefs.getIntPref(
|
||||
ShoppingSidebarParent.SIDEBAR_CLOSED_COUNT_PREF,
|
||||
0
|
||||
);
|
||||
if (
|
||||
!isKeepClosedMessageShowing &&
|
||||
sidebarClosedCount >= 4 &&
|
||||
Services.prefs.getBoolPref(
|
||||
ShoppingSidebarParent.SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF,
|
||||
true
|
||||
)
|
||||
) {
|
||||
actor.sendAsyncMessage("ShoppingSidebar:ShowKeepClosedMessage");
|
||||
return;
|
||||
}
|
||||
|
||||
actor.sendAsyncMessage("ShoppingSidebar:HideKeepClosedMessage");
|
||||
|
||||
if (sidebarClosedCount >= 6) {
|
||||
Services.prefs.setBoolPref(
|
||||
ShoppingSidebarParent.SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
Services.prefs.setIntPref(
|
||||
ShoppingSidebarParent.SIDEBAR_CLOSED_COUNT_PREF,
|
||||
sidebarClosedCount + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.toggleAllSidebars("urlBar");
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles opening or closing all Shopping sidebars.
|
||||
* Sets the active pref value for all windows to respond to.
|
||||
* params:
|
||||
*
|
||||
* @param {string?} source
|
||||
* Optional value, describes where the call came from.
|
||||
*/
|
||||
static toggleAllSidebars(source) {
|
||||
let activeState = Services.prefs.getBoolPref(
|
||||
ShoppingSidebarParent.SHOPPING_ACTIVE_PREF
|
||||
);
|
||||
Services.prefs.setBoolPref(
|
||||
ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
|
||||
!activeState
|
||||
);
|
||||
|
||||
let optedIn = Services.prefs.getIntPref(
|
||||
ShoppingSidebarParent.SHOPPING_OPTED_IN_PREF
|
||||
);
|
||||
// If the user was opted out, then clicked the button, reset the optedIn
|
||||
// pref so they see onboarding.
|
||||
if (optedIn == 2) {
|
||||
Services.prefs.setIntPref(
|
||||
ShoppingSidebarParent.SHOPPING_OPTED_IN_PREF,
|
||||
0
|
||||
);
|
||||
}
|
||||
if (source == "urlBar") {
|
||||
if (activeState) {
|
||||
Glean.shopping.surfaceClosed.record({ source: "addressBarIcon" });
|
||||
Glean.shopping.addressBarIconClicked.record({ action: "closed" });
|
||||
} else {
|
||||
Glean.shopping.addressBarIconClicked.record({ action: "opened" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShoppingSidebarManagerClass {
|
||||
#initialized = false;
|
||||
#everyWindowCallbackId = `shopping-${Services.uuid.generateUUID()}`;
|
||||
|
||||
// Public API methods - these check that we are not in private browsing
|
||||
// mode. (It might be nice to eventually shift pref checks to the public
|
||||
// API, too.)
|
||||
//
|
||||
// Note that any refactoring should preserve the PBM checks in public APIs.
|
||||
|
||||
ensureInitialized() {
|
||||
if (this.#initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateSidebarVisibility = this.updateSidebarVisibility.bind(this);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"isEnabled",
|
||||
ShoppingSidebarParent.SHOPPING_ENABLED_PREF,
|
||||
false,
|
||||
this.updateSidebarVisibility
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"optedInPref",
|
||||
"browser.shopping.experience2023.optedIn",
|
||||
null,
|
||||
this.updateSidebarVisibility
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"isActive",
|
||||
ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
|
||||
true,
|
||||
this.updateSidebarVisibility
|
||||
);
|
||||
this.updateSidebarVisibility();
|
||||
|
||||
lazy.EveryWindow.registerCallback(
|
||||
this.#everyWindowCallbackId,
|
||||
window => {
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if (isPBM) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
window.addEventListener("visibilitychange", this);
|
||||
},
|
||||
window => {
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if (isPBM) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
window.removeEventListener("visibilitychange", this);
|
||||
}
|
||||
);
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
updateSidebarVisibility() {
|
||||
this.enabled = this.isEnabled;
|
||||
for (let window of lazy.BrowserWindowTracker.orderedWindows) {
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if (isPBM) {
|
||||
continue;
|
||||
}
|
||||
this.updateSidebarVisibilityForWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
updateSidebarVisibilityForWindow(window) {
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.gBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if (isPBM) {
|
||||
return;
|
||||
}
|
||||
|
||||
let document = window.document;
|
||||
|
||||
if (!this.isActive) {
|
||||
document.querySelectorAll("shopping-sidebar").forEach(sidebar => {
|
||||
sidebar.hidden = true;
|
||||
});
|
||||
document
|
||||
.querySelectorAll(".shopping-sidebar-splitter")
|
||||
.forEach(splitter => {
|
||||
splitter.hidden = true;
|
||||
});
|
||||
}
|
||||
|
||||
this._maybeToggleButton(window.gBrowser);
|
||||
|
||||
if (!this.enabled) {
|
||||
document.querySelectorAll("shopping-sidebar").forEach(sidebar => {
|
||||
sidebar.remove();
|
||||
});
|
||||
document
|
||||
.querySelectorAll(".shopping-sidebar-splitter")
|
||||
.forEach(splitter => {
|
||||
splitter.remove();
|
||||
});
|
||||
let button = document.getElementById("shopping-sidebar-button");
|
||||
if (button) {
|
||||
button.hidden = true;
|
||||
// Reset attributes to defaults.
|
||||
button.setAttribute("shoppingsidebaropen", false);
|
||||
document.l10n.setAttributes(button, "shopping-sidebar-open-button2");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let { selectedBrowser, currentURI } = window.gBrowser;
|
||||
this._maybeToggleSidebar(selectedBrowser, currentURI, 0, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by TabsProgressListener whenever any browser navigates from one
|
||||
* URL to another.
|
||||
* Note that this includes hash changes / pushState navigations, because
|
||||
* those can be significant for us.
|
||||
*/
|
||||
onLocationChange(aBrowser, aLocationURI, aFlags) {
|
||||
let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(aBrowser.ownerGlobal);
|
||||
if (isPBM || !this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
lazy.ShoppingUtils.onLocationChange(aLocationURI, aFlags);
|
||||
|
||||
this._maybeToggleButton(aBrowser.getTabBrowser());
|
||||
this._maybeToggleSidebar(aBrowser, aLocationURI, aFlags, true);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "TabSelect": {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
this.updateSidebarVisibility();
|
||||
if (event.detail?.previousTab.linkedBrowser) {
|
||||
this._updateBCActiveness(event.detail.previousTab.linkedBrowser);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "visibilitychange": {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
let { gBrowser } = event.target.ownerGlobal.top;
|
||||
if (!gBrowser) {
|
||||
return;
|
||||
}
|
||||
this.updateSidebarVisibilityForWindow(event.target.ownerGlobal.top);
|
||||
this._updateBCActiveness(gBrowser.selectedBrowser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private API methods - these assume we are not in private browsing
|
||||
// mode. (It might be nice to eventually shift pref checks to the public
|
||||
// API, too.)
|
||||
|
||||
_maybeToggleSidebar(aBrowser, aLocationURI, aFlags, aIsNavigation) {
|
||||
let gBrowser = aBrowser.getTabBrowser();
|
||||
let document = aBrowser.ownerDocument;
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let browserPanel = gBrowser.getPanel(aBrowser);
|
||||
let sidebar = browserPanel.querySelector("shopping-sidebar");
|
||||
let actor;
|
||||
if (sidebar) {
|
||||
let { browsingContext } = sidebar.querySelector("browser");
|
||||
let global = browsingContext.currentWindowGlobal;
|
||||
actor = global.getExistingActor("ShoppingSidebar");
|
||||
}
|
||||
let isProduct = lazy.isProductURL(aLocationURI);
|
||||
if (isProduct && this.isActive) {
|
||||
if (!sidebar) {
|
||||
sidebar = document.createXULElement("shopping-sidebar");
|
||||
sidebar.hidden = false;
|
||||
let splitter = document.createXULElement("splitter");
|
||||
splitter.classList.add("sidebar-splitter", "shopping-sidebar-splitter");
|
||||
browserPanel.appendChild(splitter);
|
||||
browserPanel.appendChild(sidebar);
|
||||
} else {
|
||||
actor?.updateCurrentURL(aLocationURI, aFlags);
|
||||
sidebar.hidden = false;
|
||||
let splitter = browserPanel.querySelector(".shopping-sidebar-splitter");
|
||||
splitter.hidden = false;
|
||||
}
|
||||
} else if (sidebar && !sidebar.hidden) {
|
||||
actor?.updateCurrentURL(null);
|
||||
sidebar.hidden = true;
|
||||
let splitter = browserPanel.querySelector(".shopping-sidebar-splitter");
|
||||
splitter.hidden = true;
|
||||
}
|
||||
|
||||
this._updateBCActiveness(aBrowser);
|
||||
this._setShoppingButtonState(aBrowser);
|
||||
|
||||
// Note: (bug 1868602) only record surface displayed telemetry if:
|
||||
// - the foregrounded tab navigates to a product page with sidebar visible,
|
||||
// - a product page tab loaded in the background is foregrounded, or
|
||||
// - a foregrounded product page tab was loaded with the sidebar hidden and
|
||||
// now the sidebar has been shown.
|
||||
if (
|
||||
this.enabled &&
|
||||
lazy.ShoppingUtils.isProductPageNavigation(aLocationURI, aFlags)
|
||||
) {
|
||||
if (
|
||||
this.isActive &&
|
||||
aBrowser === gBrowser.selectedBrowser &&
|
||||
(aIsNavigation || aBrowser.isDistinctProductPageVisit)
|
||||
) {
|
||||
Glean.shopping.surfaceDisplayed.record();
|
||||
delete aBrowser.isDistinctProductPageVisit;
|
||||
} else if (aIsNavigation) {
|
||||
aBrowser.isDistinctProductPageVisit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isProduct) {
|
||||
// This is the auto-enable behavior that toggles the `active` pref. It
|
||||
// must be at the end of this function, or 2 sidebars could be created.
|
||||
lazy.ShoppingUtils.handleAutoActivateOnProduct();
|
||||
|
||||
if (!this.isActive) {
|
||||
lazy.ShoppingUtils.sendTrigger({
|
||||
browser: aBrowser,
|
||||
id: "shoppingProductPageWithSidebarClosed",
|
||||
context: { isSidebarClosing: !aIsNavigation && !!sidebar },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_maybeToggleButton(gBrowser) {
|
||||
let optedOut = this.optedInPref === 2;
|
||||
if (this.enabled && optedOut) {
|
||||
this._setShoppingButtonState(gBrowser.selectedBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
_updateBCActiveness(aBrowser) {
|
||||
let gBrowser = aBrowser.getTabBrowser();
|
||||
let document = aBrowser.ownerDocument;
|
||||
let browserPanel = gBrowser.getPanel(aBrowser);
|
||||
let sidebar = browserPanel.querySelector("shopping-sidebar");
|
||||
if (!sidebar) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Tell Gecko when the sidebar visibility changes to avoid background
|
||||
// sidebars taking more CPU / energy than needed.
|
||||
sidebar.querySelector("browser").docShellIsActive =
|
||||
!document.hidden &&
|
||||
aBrowser == gBrowser.selectedBrowser &&
|
||||
!sidebar.hidden;
|
||||
} catch (ex) {
|
||||
// The setter can throw and we do need to run the rest of this
|
||||
// code in that case.
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
_setShoppingButtonState(aBrowser) {
|
||||
let gBrowser = aBrowser.getTabBrowser();
|
||||
let document = aBrowser.ownerDocument;
|
||||
if (aBrowser !== gBrowser.selectedBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let button = document.getElementById("shopping-sidebar-button");
|
||||
|
||||
let isCurrentBrowserProduct = lazy.isProductURL(
|
||||
gBrowser.selectedBrowser.currentURI
|
||||
);
|
||||
|
||||
// Only record if the state of the icon will change from hidden to visible.
|
||||
if (button.hidden && isCurrentBrowserProduct) {
|
||||
Glean.shopping.addressBarIconDisplayed.record();
|
||||
}
|
||||
|
||||
button.hidden = !isCurrentBrowserProduct;
|
||||
button.setAttribute("shoppingsidebaropen", !!this.isActive);
|
||||
let l10nId = this.isActive
|
||||
? "shopping-sidebar-close-button2"
|
||||
: "shopping-sidebar-open-button2";
|
||||
document.l10n.setAttributes(button, l10nId);
|
||||
}
|
||||
}
|
||||
|
||||
const ShoppingSidebarManager = new ShoppingSidebarManagerClass();
|
||||
export { ShoppingSidebarManager };
|
||||
@@ -1,422 +0,0 @@
|
||||
/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
|
||||
isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
|
||||
getProductIdFromURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
});
|
||||
|
||||
const OPTED_IN_PREF = "browser.shopping.experience2023.optedIn";
|
||||
const ACTIVE_PREF = "browser.shopping.experience2023.active";
|
||||
const LAST_AUTO_ACTIVATE_PREF =
|
||||
"browser.shopping.experience2023.lastAutoActivate";
|
||||
const AUTO_ACTIVATE_COUNT_PREF =
|
||||
"browser.shopping.experience2023.autoActivateCount";
|
||||
const ADS_USER_ENABLED_PREF = "browser.shopping.experience2023.ads.userEnabled";
|
||||
const AUTO_OPEN_ENABLED_PREF =
|
||||
"browser.shopping.experience2023.autoOpen.enabled";
|
||||
const AUTO_OPEN_USER_ENABLED_PREF =
|
||||
"browser.shopping.experience2023.autoOpen.userEnabled";
|
||||
const SIDEBAR_CLOSED_COUNT_PREF =
|
||||
"browser.shopping.experience2023.sidebarClosedCount";
|
||||
|
||||
const CFR_FEATURES_PREF =
|
||||
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features";
|
||||
|
||||
const ENABLED_PREF = "browser.shopping.experience2023.enabled";
|
||||
|
||||
export const ShoppingUtils = {
|
||||
initialized: false,
|
||||
registered: false,
|
||||
handledAutoActivate: false,
|
||||
enabled: false,
|
||||
everyWindowCallbackId: `shoppingutils-${Services.uuid.generateUUID()}`,
|
||||
|
||||
_updatePrefVariables() {
|
||||
this.enabled = Services.prefs.getBoolPref(ENABLED_PREF, false);
|
||||
},
|
||||
|
||||
onPrefUpdate(_subject, topic) {
|
||||
if (topic !== "nsPref:changed") {
|
||||
return;
|
||||
}
|
||||
if (this.initialized) {
|
||||
ShoppingUtils.uninit(true);
|
||||
Glean.shoppingSettings.nimbusDisabledShopping.set(true);
|
||||
}
|
||||
this._updatePrefVariables();
|
||||
|
||||
if (this.enabled) {
|
||||
ShoppingUtils.init();
|
||||
Glean.shoppingSettings.nimbusDisabledShopping.set(false);
|
||||
}
|
||||
},
|
||||
|
||||
// Runs once per session:
|
||||
// * at application startup, with startup idle tasks,
|
||||
// * or after the user is enrolled in the Nimbus experiment.
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.onPrefUpdate = this.onPrefUpdate.bind(this);
|
||||
this.onActiveUpdate = this.onActiveUpdate.bind(this);
|
||||
|
||||
if (!this.registered) {
|
||||
// Note (bug 1855545): we must set `this.registered` before calling
|
||||
// `onUpdate`, as it will immediately invoke `this.onPrefUpdate`,
|
||||
// which in turn calls `ShoppingUtils.init`, creating an infinite loop.
|
||||
this.registered = true;
|
||||
Services.prefs.addObserver(ENABLED_PREF, this.onPrefUpdate);
|
||||
this._updatePrefVariables();
|
||||
}
|
||||
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do startup-time stuff here, like recording startup-time glean events
|
||||
// or adjusting onboarding-related prefs once per session.
|
||||
|
||||
this.setOnUpdate(undefined, undefined, this.optedIn);
|
||||
this.recordUserAdsPreference();
|
||||
this.recordUserAutoOpenPreference();
|
||||
|
||||
if (this.isAutoOpenEligible()) {
|
||||
Services.prefs.setBoolPref(ACTIVE_PREF, true);
|
||||
}
|
||||
Services.prefs.addObserver(ACTIVE_PREF, this.onActiveUpdate);
|
||||
|
||||
Services.prefs.setIntPref(SIDEBAR_CLOSED_COUNT_PREF, 0);
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs when:
|
||||
* - the shopping2023 enabled pref is changed,
|
||||
* - the user is unenrolled from the Nimbus experiment,
|
||||
* - or at shutdown, after quit-application-granted.
|
||||
*
|
||||
* @param {boolean} soft
|
||||
* If this is a soft uninit, for a pref change, we want to keep the
|
||||
* pref listeners around incase they are changed again.
|
||||
*/
|
||||
uninit(soft) {
|
||||
if (!this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do shutdown-time stuff here, like firing glean pings or modifying any
|
||||
// prefs for onboarding.
|
||||
|
||||
Services.prefs.removeObserver(ACTIVE_PREF, this.onActiveUpdate);
|
||||
|
||||
if (!soft) {
|
||||
this.registered = false;
|
||||
Services.prefs.removeObserver(ENABLED_PREF, this.onPrefUpdate);
|
||||
}
|
||||
|
||||
this.initialized = false;
|
||||
},
|
||||
|
||||
isProductPageNavigation(aLocationURI, aFlags) {
|
||||
if (!lazy.isProductURL(aLocationURI)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ignore same-document navigation, except in the case of Walmart
|
||||
// as they use pushState to navigate between pages.
|
||||
let isWalmart = aLocationURI.host.includes("walmart");
|
||||
let isNewDocument = !aFlags;
|
||||
|
||||
let isSameDocument =
|
||||
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
|
||||
let isReload = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD;
|
||||
let isSessionRestore =
|
||||
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE;
|
||||
|
||||
// Unfortunately, Walmart sometimes double-fires history manipulation
|
||||
// events when navigating between product pages. To dedupe, cache the
|
||||
// last visited Walmart URL just for a few milliseconds, so we can avoid
|
||||
// double-counting such navigations.
|
||||
if (isWalmart) {
|
||||
if (
|
||||
this.lastWalmartURI &&
|
||||
aLocationURI.equalsExceptRef(this.lastWalmartURI)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
this.lastWalmartURI = aLocationURI;
|
||||
lazy.setTimeout(() => {
|
||||
this.lastWalmartURI = null;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return (
|
||||
// On initial visit to a product page, even from another domain, both a page
|
||||
// load and a pushState will be triggered by Walmart, so this will
|
||||
// capture only a single displayed event.
|
||||
(!isWalmart && !!(isNewDocument || isReload || isSessionRestore)) ||
|
||||
(isWalmart && !!isSameDocument)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Similar to isProductPageNavigation but compares the
|
||||
* current location URI to a previous location URI and
|
||||
* checks if the URI and product has changed.
|
||||
*
|
||||
* This lets us avoid issues with over-counting products
|
||||
* that have multiple loads or history changes.
|
||||
*
|
||||
* @param {nsIURI} aLocationURI
|
||||
* The current location.
|
||||
* @param {integer} aFlags
|
||||
* The load flags or null.
|
||||
* @param {nsIURI} aPreviousURI
|
||||
* A previous product URI or null.
|
||||
* @returns {boolean} isNewProduct
|
||||
*/
|
||||
hasLocationChanged(aLocationURI, aFlags, aPreviousURI) {
|
||||
let isReload = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD;
|
||||
let isSessionRestore =
|
||||
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE;
|
||||
|
||||
// If we have reloaded, restored or there isn't a previous URI
|
||||
// this is a location change.
|
||||
if (isReload || isSessionRestore || !aPreviousURI) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let isCurrentLocationProduct = lazy.isProductURL(aLocationURI);
|
||||
let isPrevLocationProduct = lazy.isProductURL(aPreviousURI);
|
||||
|
||||
// If the locations are not products, we can just compare URIs.
|
||||
if (!isCurrentLocationProduct && !isPrevLocationProduct) {
|
||||
return aLocationURI.equalsExceptRef(aPreviousURI);
|
||||
}
|
||||
|
||||
// If one of the URIs is not a product url, but the other is
|
||||
// this is a location change.
|
||||
if (!isCurrentLocationProduct || !isPrevLocationProduct) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If URIs are both products we will need to check,
|
||||
// if the product have changed by comparing them.
|
||||
let isSameProduct = this.isSameProduct(aLocationURI, aPreviousURI);
|
||||
return !isSameProduct;
|
||||
},
|
||||
|
||||
// For enabled users, increment a
|
||||
// counter when they visit supported product pages.
|
||||
recordExposure() {
|
||||
if (this.enabled) {
|
||||
Glean.shopping.productPageVisits.add(1);
|
||||
}
|
||||
},
|
||||
|
||||
setOnUpdate(_pref, _prev, current) {
|
||||
Glean.shoppingSettings.componentOptedOut.set(current === 2);
|
||||
Glean.shoppingSettings.hasOnboarded.set(current > 0);
|
||||
},
|
||||
|
||||
recordUserAdsPreference() {
|
||||
Glean.shoppingSettings.disabledAds.set(!ShoppingUtils.adsUserEnabled);
|
||||
},
|
||||
|
||||
recordUserAutoOpenPreference() {
|
||||
Glean.shoppingSettings.autoOpenUserDisabled.set(
|
||||
!ShoppingUtils.autoOpenUserEnabled
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* If the user has not opted in, automatically set the sidebar to `active` if:
|
||||
* 1. The sidebar has not already been automatically set to `active` twice.
|
||||
* 2. It's been at least 24 hours since the user last saw the sidebar because
|
||||
* of this auto-activation behavior.
|
||||
* 3. This method has not already been called (handledAutoActivate is false)
|
||||
*/
|
||||
handleAutoActivateOnProduct() {
|
||||
let shouldAutoActivate = false;
|
||||
|
||||
if (
|
||||
!this.handledAutoActivate &&
|
||||
!this.optedIn &&
|
||||
this.cfrFeatures &&
|
||||
this.autoOpenEnabled
|
||||
) {
|
||||
let autoActivateCount = Services.prefs.getIntPref(
|
||||
AUTO_ACTIVATE_COUNT_PREF,
|
||||
0
|
||||
);
|
||||
let lastAutoActivate = Services.prefs.getIntPref(
|
||||
LAST_AUTO_ACTIVATE_PREF,
|
||||
0
|
||||
);
|
||||
let now = Date.now() / 1000;
|
||||
// If we automatically set `active` to true in a previous session less
|
||||
// than 24 hours ago, set it to false now. This is done to prevent the
|
||||
// auto-activation state from persisting between sessions. Effectively,
|
||||
// the auto-activation will persist until either 1) the sidebar is closed,
|
||||
// or 2) Firefox restarts.
|
||||
if (now - lastAutoActivate < 24 * 60 * 60) {
|
||||
Services.prefs.setBoolPref(ACTIVE_PREF, false);
|
||||
}
|
||||
// Set active to true if we haven't done so recently nor more than twice.
|
||||
else if (autoActivateCount < 2) {
|
||||
Services.prefs.setBoolPref(ACTIVE_PREF, true);
|
||||
shouldAutoActivate = true;
|
||||
Services.prefs.setIntPref(
|
||||
AUTO_ACTIVATE_COUNT_PREF,
|
||||
autoActivateCount + 1
|
||||
);
|
||||
Services.prefs.setIntPref(LAST_AUTO_ACTIVATE_PREF, now);
|
||||
}
|
||||
}
|
||||
this.handledAutoActivate = true;
|
||||
return shouldAutoActivate;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a Shopping-related trigger message to ASRouter.
|
||||
*
|
||||
* @param {object} trigger The trigger object to send to ASRouter.
|
||||
* @param {object} trigger.context Additional trigger properties to pass to
|
||||
* the targeting context.
|
||||
* @param {string} trigger.id The id of the trigger.
|
||||
* @param {MozBrowser} trigger.browser The browser to associate with the
|
||||
* trigger. (This can determine the tab/window the message is shown in,
|
||||
* depending on the message surface)
|
||||
*/
|
||||
async sendTrigger(trigger) {
|
||||
await lazy.ASRouter.waitForInitialized;
|
||||
await lazy.ASRouter.sendTriggerMessage(trigger);
|
||||
},
|
||||
|
||||
onActiveUpdate(subject, topic, data) {
|
||||
if (data !== ACTIVE_PREF || topic !== "nsPref:changed") {
|
||||
return;
|
||||
}
|
||||
|
||||
let newValue = Services.prefs.getBoolPref(ACTIVE_PREF);
|
||||
if (newValue === false) {
|
||||
ShoppingUtils.resetActiveOnNextProductPage = true;
|
||||
}
|
||||
},
|
||||
|
||||
isAutoOpenEligible() {
|
||||
return (
|
||||
this.optedIn === 1 && this.autoOpenEnabled && this.autoOpenUserEnabled
|
||||
);
|
||||
},
|
||||
|
||||
onLocationChange(aLocationURI, aFlags) {
|
||||
let isProductPageNavigation = this.isProductPageNavigation(
|
||||
aLocationURI,
|
||||
aFlags
|
||||
);
|
||||
|
||||
if (isProductPageNavigation) {
|
||||
this.recordExposure();
|
||||
}
|
||||
|
||||
if (
|
||||
this.isAutoOpenEligible() &&
|
||||
this.resetActiveOnNextProductPage &&
|
||||
isProductPageNavigation
|
||||
) {
|
||||
this.resetActiveOnNextProductPage = false;
|
||||
Services.prefs.setBoolPref(ACTIVE_PREF, true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if two URIs represent the same product by
|
||||
* comparing URLs and then parsed product ID.
|
||||
*
|
||||
* @param {nsIURI} aURI
|
||||
* @param {nsIURI} bURI
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSameProduct(aURI, bURI) {
|
||||
if (!aURI || !bURI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the URIs are equal and are products.
|
||||
if (aURI.equalsExceptRef(bURI)) {
|
||||
return lazy.isProductURL(aURI);
|
||||
}
|
||||
|
||||
// Check if the product ids are the same:
|
||||
let aProductID = lazy.getProductIdFromURL(aURI);
|
||||
let bProductID = lazy.getProductIdFromURL(bURI);
|
||||
|
||||
if (!aProductID || !bProductID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return aProductID === bProductID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes browser `isDistinctProductPageVisit` flag that indicates
|
||||
* a tab has an unhandled product navigation.
|
||||
*
|
||||
* @param {browser} browser
|
||||
*/
|
||||
clearIsDistinctProductPageVisitFlag(browser) {
|
||||
if (browser.isDistinctProductPageVisit) {
|
||||
delete browser.isDistinctProductPageVisit;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
ShoppingUtils,
|
||||
"optedIn",
|
||||
OPTED_IN_PREF,
|
||||
0,
|
||||
ShoppingUtils.setOnUpdate
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
ShoppingUtils,
|
||||
"cfrFeatures",
|
||||
CFR_FEATURES_PREF,
|
||||
true
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
ShoppingUtils,
|
||||
"adsUserEnabled",
|
||||
ADS_USER_ENABLED_PREF,
|
||||
false,
|
||||
ShoppingUtils.recordUserAdsPreference
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
ShoppingUtils,
|
||||
"autoOpenEnabled",
|
||||
AUTO_OPEN_ENABLED_PREF,
|
||||
false
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
ShoppingUtils,
|
||||
"autoOpenUserEnabled",
|
||||
AUTO_OPEN_USER_ENABLED_PREF,
|
||||
false,
|
||||
ShoppingUtils.recordUserAutoOpenPreference
|
||||
);
|
||||
@@ -1,41 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
/**
|
||||
* Class for displaying the adjusted ratings for a given product.
|
||||
*/
|
||||
class AdjustedRating extends MozLitElement {
|
||||
static properties = {
|
||||
rating: { type: Number, reflect: true },
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.rating && this.rating !== 0) {
|
||||
this.hidden = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.hidden = false;
|
||||
|
||||
return html`
|
||||
<shopping-card
|
||||
data-l10n-id="shopping-adjusted-rating-label"
|
||||
data-l10n-attrs="label"
|
||||
rating=${this.rating}
|
||||
>
|
||||
<div slot="content">
|
||||
<span
|
||||
data-l10n-id="shopping-adjusted-rating-based-reliable-reviews"
|
||||
></span>
|
||||
</div>
|
||||
</shopping-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("adjusted-rating", AdjustedRating);
|
||||
@@ -1,39 +0,0 @@
|
||||
/* 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 url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
#analysis-explainer-wrapper p,
|
||||
.analysis-explainer-grading-scale-description {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
#analysis-explainer-grading-scale-wrapper {
|
||||
margin-inline-start: 0.54em;
|
||||
}
|
||||
|
||||
#analysis-explainer-grading-scale-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.analysis-explainer-grading-scale-entry {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
gap: 0.54rem;
|
||||
padding: 0.54em;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.analysis-explainer-grading-scale-letters {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.2rem;
|
||||
width: 3.47em;
|
||||
}
|
||||
|
||||
.analysis-explainer-grading-scale-description {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/shopping-card.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/letter-grade.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://global/content/elements/moz-support-link.mjs";
|
||||
|
||||
const VALID_EXPLAINER_L10N_IDS = new Map([
|
||||
["reliable", "shopping-analysis-explainer-review-grading-scale-reliable"],
|
||||
["mixed", "shopping-analysis-explainer-review-grading-scale-mixed"],
|
||||
["unreliable", "shopping-analysis-explainer-review-grading-scale-unreliable"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Class for displaying details about letter grades, adjusted rating, and highlights.
|
||||
*/
|
||||
class AnalysisExplainer extends MozLitElement {
|
||||
static properties = {
|
||||
productUrl: { type: String, reflect: true },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
reviewQualityExplainerLink: "#review-quality-url",
|
||||
};
|
||||
}
|
||||
|
||||
getGradesDescriptionTemplate() {
|
||||
return html`
|
||||
<section id="analysis-explainer-grades-wrapper">
|
||||
<p data-l10n-id="shopping-analysis-explainer-grades-intro"></p>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
createGradingScaleEntry(letters, descriptionL10nId) {
|
||||
let letterGradesTemplate = [];
|
||||
for (let letter of letters) {
|
||||
letterGradesTemplate.push(
|
||||
html`<letter-grade letter=${letter}></letter-grade>`
|
||||
);
|
||||
}
|
||||
return html`
|
||||
<div class="analysis-explainer-grading-scale-entry">
|
||||
<dt class="analysis-explainer-grading-scale-term">
|
||||
<span class="analysis-explainer-grading-scale-letters">
|
||||
${letterGradesTemplate}
|
||||
</span>
|
||||
</dt>
|
||||
<dd
|
||||
class="analysis-explainer-grading-scale-description"
|
||||
data-l10n-id=${descriptionL10nId}
|
||||
></dd>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getGradingScaleListTemplate() {
|
||||
return html`
|
||||
<section id="analysis-explainer-grading-scale-wrapper">
|
||||
<dl id="analysis-explainer-grading-scale-list">
|
||||
${this.createGradingScaleEntry(
|
||||
["A", "B"],
|
||||
VALID_EXPLAINER_L10N_IDS.get("reliable")
|
||||
)}
|
||||
${this.createGradingScaleEntry(
|
||||
["C"],
|
||||
VALID_EXPLAINER_L10N_IDS.get("mixed")
|
||||
)}
|
||||
${this.createGradingScaleEntry(
|
||||
["D", "F"],
|
||||
VALID_EXPLAINER_L10N_IDS.get("unreliable")
|
||||
)}
|
||||
</dl>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
getRetailerDisplayName() {
|
||||
if (!this.productUrl) {
|
||||
return null;
|
||||
}
|
||||
let url = new URL(this.productUrl);
|
||||
let hostname = url.hostname;
|
||||
let displayNames = {
|
||||
"www.amazon.com": "Amazon",
|
||||
"www.amazon.de": "Amazon",
|
||||
"www.amazon.fr": "Amazon",
|
||||
// only other regional domain is bestbuy.ca
|
||||
"www.bestbuy.com": "Best Buy",
|
||||
// regional urls redirect to walmart.com
|
||||
"www.walmart.com": "Walmart",
|
||||
};
|
||||
return displayNames[hostname];
|
||||
}
|
||||
|
||||
handleReviewQualityUrlClicked(e) {
|
||||
if (e.target.localName == "a" && e.button == 0) {
|
||||
Glean.shopping.surfaceShowQualityExplainerUrlClicked.record();
|
||||
}
|
||||
}
|
||||
|
||||
createReviewsExplainer() {
|
||||
const retailer = this.getRetailerDisplayName();
|
||||
|
||||
if (retailer) {
|
||||
return html`
|
||||
<p
|
||||
data-l10n-id="shopping-analysis-explainer-highlights-description"
|
||||
data-l10n-args=${JSON.stringify({ retailer })}
|
||||
></p>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<p
|
||||
data-l10n-id="shopping-analysis-explainer-highlights-description-unknown-retailer"
|
||||
></p>
|
||||
`;
|
||||
}
|
||||
|
||||
// Bug 1857620: rather than manually set the utm parameters on the SUMO link,
|
||||
// we should instead update moz-support-link to allow arbitrary utm parameters.
|
||||
render() {
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/analysis-explainer.css"
|
||||
/>
|
||||
<shopping-card
|
||||
id="shopping-analysis-explainer-label"
|
||||
data-l10n-id="shopping-analysis-explainer-label"
|
||||
data-l10n-attrs="label"
|
||||
type="accordion"
|
||||
>
|
||||
<div slot="content">
|
||||
<div id="analysis-explainer-wrapper">
|
||||
<p data-l10n-id="shopping-analysis-explainer-intro2"></p>
|
||||
${this.getGradesDescriptionTemplate()}
|
||||
${this.getGradingScaleListTemplate()}
|
||||
<p
|
||||
data-l10n-id="shopping-analysis-explainer-adjusted-rating-description"
|
||||
></p>
|
||||
${this.createReviewsExplainer()}
|
||||
<p
|
||||
data-l10n-id="shopping-analysis-explainer-learn-more2"
|
||||
@click=${this.handleReviewQualityUrlClicked}
|
||||
>
|
||||
<a
|
||||
id="review-quality-url"
|
||||
data-l10n-name="review-quality-url"
|
||||
target="_blank"
|
||||
href="${window.RPMGetFormatURLPref(
|
||||
"app.support.baseURL"
|
||||
)}review-checker-review-quality?as=u&utm_source=inproduct&utm_campaign=learn-more&utm_term=core-sidebar"
|
||||
></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</shopping-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("analysis-explainer", AnalysisExplainer);
|
||||
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,6 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
|
||||
<path fill-rule="evenodd" d="M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2H3Zm-.5 2a.5.5 0 0 1 .5-.5h2v5a.75.75 0 0 0 1.114.656L8 7.108l1.886 1.048A.75.75 0 0 0 11 7.5v-5h2a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5V3Zm7-.5h-3v3.725l1.136-.63a.75.75 0 0 1 .728 0l1.136.63V2.5Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 653 B |
@@ -1,41 +0,0 @@
|
||||
<!-- 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 https://mozilla.org/MPL/2.0/. -->
|
||||
<svg width="352" height="214" viewBox="0 0 352 214" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_860_36488)">
|
||||
<rect x="-5.65186" y="-7.44189" width="357.558" height="250" fill="hsla(252, 88%, 70%, 22%)"/>
|
||||
<g filter="url(#filter0_d_860_36488)">
|
||||
<rect x="-40.5356" y="52.4418" width="340.116" height="207.558" rx="18.6047" fill="#F9F9FB"/>
|
||||
</g>
|
||||
<path d="M-23.6753 52.4418H282.139C291.772 52.4418 299.581 60.2508 299.581 69.8836V173.953H-23.6753V52.4418Z" fill="#E0E0E6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M273.703 137.919H258.599C258.366 137.919 258.143 137.826 257.978 137.662C257.813 137.497 257.721 137.274 257.721 137.041C257.721 136.808 257.813 136.585 257.978 136.42C258.143 136.255 258.366 136.163 258.599 136.163H273.703C273.936 136.163 274.159 136.255 274.324 136.42C274.489 136.585 274.581 136.808 274.581 137.041C274.581 137.274 274.489 137.497 274.324 137.662C274.159 137.826 273.936 137.919 273.703 137.919ZM273.703 143.539H258.599C258.366 143.539 258.143 143.447 257.978 143.282C257.813 143.118 257.721 142.894 257.721 142.661C257.721 142.428 257.813 142.205 257.978 142.04C258.143 141.876 258.366 141.783 258.599 141.783H273.703C273.936 141.783 274.159 141.876 274.324 142.04C274.489 142.205 274.581 142.428 274.581 142.661C274.581 142.894 274.489 143.118 274.324 143.282C274.159 143.447 273.936 143.539 273.703 143.539ZM258.599 149.159H273.703C273.936 149.159 274.159 149.067 274.324 148.902C274.489 148.737 274.581 148.514 274.581 148.281C274.581 148.048 274.489 147.825 274.324 147.66C274.159 147.495 273.936 147.403 273.703 147.403H258.599C258.366 147.403 258.143 147.495 257.978 147.66C257.813 147.825 257.721 148.048 257.721 148.281C257.721 148.514 257.813 148.737 257.978 148.902C258.143 149.067 258.366 149.159 258.599 149.159Z" fill="#8F8F9D"/>
|
||||
<path d="M-23.6753 52.4418H282.139C291.772 52.4418 299.581 60.2508 299.581 69.8836V111.163H-23.6753V52.4418Z" fill="#CFCFD8"/>
|
||||
<path d="M180.51 82.0315V83.363H167.208V82.0315H180.51Z" fill="#8F8F9D"/>
|
||||
<path d="M226.871 76.7108V90.0127H213.57V76.7108H226.871ZM225.54 78.0423H214.901V88.6812H225.54V78.0423Z" fill="#8F8F9D"/>
|
||||
<path d="M267.524 83.3619L273.233 89.071L272.291 90.0128L266.582 84.3037L260.873 90.0128L259.931 89.071L265.64 83.3619L259.931 77.6527L260.873 76.7109L266.582 82.4201L272.291 76.7109L273.233 77.6527L267.524 83.3619Z" fill="#8F8F9D"/>
|
||||
<rect x="-50.75" y="120.134" width="220.43" height="46.0116" rx="4.40116" fill="white" stroke="#5B5B66" stroke-width="0.5"/>
|
||||
<rect x="84.1739" y="125.988" width="34.8837" height="34.8837" rx="5.52326" stroke="#0060DF" stroke-width="1.74419"/>
|
||||
<rect x="-19.605" y="173.953" width="319.186" height="76.7442" fill="#F9F9FB"/>
|
||||
<path d="M103.624 132.093H94.9371V134.13H103.2L110.926 141.88L112.372 140.443L104.346 132.393C104.155 132.201 103.895 132.093 103.624 132.093Z" fill="#15141A"/>
|
||||
<path d="M98.3554 144.177L100.004 142.53L98.3554 140.883L96.7069 142.53L98.3554 144.177Z" fill="#15141A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.0229 137.187C92.0229 136.624 92.4796 136.168 93.0429 136.168H101.511C101.782 136.168 102.041 136.275 102.233 136.466L110.083 144.308C111.222 145.445 111.222 147.287 110.083 148.424L104.004 154.496C102.866 155.633 101.022 155.633 99.8833 154.496L92.3217 146.943C92.1304 146.752 92.0229 146.493 92.0229 146.223V137.187ZM94.0628 138.205V145.801L101.326 153.055C101.668 153.397 102.22 153.397 102.562 153.055L108.641 146.983C108.983 146.642 108.983 146.09 108.641 145.749L101.089 138.205H94.0628Z" fill="#15141A"/>
|
||||
<path d="M138.723 154.174C138.205 154.174 137.676 154.012 137.24 153.698C136.392 153.093 136.016 152.058 136.275 151.058L137.523 146.232L133.638 143.046C132.837 142.384 132.519 141.325 132.849 140.349C133.167 139.372 134.05 138.686 135.097 138.628L140.136 138.302L141.996 133.674C142.385 132.721 143.303 132.093 144.351 132.093C145.398 132.093 146.317 132.709 146.705 133.674L148.565 138.302L153.604 138.628C154.64 138.698 155.523 139.372 155.852 140.349C156.17 141.325 155.864 142.395 155.064 143.046L151.179 146.232L152.427 151.058C152.686 152.058 152.309 153.093 151.461 153.698C150.614 154.302 149.495 154.337 148.612 153.791L144.351 151.128L140.089 153.791C139.677 154.046 139.206 154.186 138.735 154.186L138.723 154.174ZM144.339 134.116C144.221 134.116 143.997 134.151 143.903 134.407L141.796 139.639C141.655 140 141.302 140.256 140.901 140.279L135.215 140.639C134.933 140.663 134.827 140.849 134.791 140.965C134.756 141.058 134.721 141.291 134.944 141.477L139.324 145.07C139.63 145.325 139.759 145.721 139.665 146.105L138.264 151.558C138.194 151.825 138.347 151.988 138.441 152.058C138.535 152.128 138.735 152.221 138.971 152.07L143.786 149.058C144.127 148.849 144.551 148.849 144.88 149.058L149.695 152.07C149.931 152.221 150.131 152.128 150.225 152.058C150.319 151.988 150.472 151.825 150.402 151.558L148.989 146.105C148.895 145.721 149.024 145.325 149.33 145.07L153.71 141.477C153.933 141.291 153.886 141.058 153.863 140.977C153.827 140.86 153.722 140.674 153.439 140.651L147.753 140.291C147.353 140.267 147.011 140.012 146.858 139.651L144.751 134.418C144.645 134.163 144.433 134.128 144.315 134.128L144.339 134.116Z" fill="#8F8F9D"/>
|
||||
<line x1="299.581" y1="111.413" x2="-19.605" y2="111.413" stroke="#5B5B66" stroke-width="0.5"/>
|
||||
<line x1="299.581" y1="174.203" x2="-35.3027" y2="174.203" stroke="#5B5B66" stroke-width="0.5"/>
|
||||
<path d="M-30.6519 52.4418H280.976C291.251 52.4418 299.581 60.7714 299.581 71.0464V242.558" stroke="#5B5B66" stroke-width="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_860_36488" x="-49.2566" y="40.2325" width="361.046" height="228.488" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="1.74419" dy="-1.74419"/>
|
||||
<feGaussianBlur stdDeviation="5.23256"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.227451 0 0 0 0 0.223529 0 0 0 0 0.266667 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_860_36488"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_860_36488" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_860_36488">
|
||||
<rect width="352" height="214" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,7 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
|
||||
<path d="M8.035 9.713 11 6.748l-1.061-1.06-2.434 2.434L6.06 6.677 5 7.737l1.975 1.976a.75.75 0 0 0 1.06 0Z"/>
|
||||
<path d="M10.586 1.723 9.414.552a2 2 0 0 0-2.828 0L5.414 1.723H3.757a2 2 0 0 0-2 2v1.656L.585 6.552a2 2 0 0 0 0 2.828l1.172 1.172v1.657a2 2 0 0 0 2 2h1.657l1.172 1.171a2 2 0 0 0 2.828 0l1.172-1.171h1.657a2 2 0 0 0 2-2v-1.657l1.172-1.172a2 2 0 0 0 0-2.828l-1.172-1.173V3.723a2 2 0 0 0-2-2h-1.657Zm-2.94-.11a.5.5 0 0 1 .708 0l1.392 1.39c.14.141.331.22.53.22h1.967a.5.5 0 0 1 .5.5V5.69c0 .199.079.39.22.53l1.39 1.392a.5.5 0 0 1 0 .708l-1.39 1.39a.75.75 0 0 0-.22.531v1.968a.5.5 0 0 1-.5.5h-1.968a.75.75 0 0 0-.53.22l-1.391 1.39a.5.5 0 0 1-.708 0l-1.39-1.39a.75.75 0 0 0-.531-.22H3.757a.5.5 0 0 1-.5-.5v-1.968a.75.75 0 0 0-.22-.53L1.647 8.32a.5.5 0 0 1 0-.708l1.39-1.392a.75.75 0 0 0 .22-.53V3.723a.5.5 0 0 1 .5-.5h1.968a.75.75 0 0 0 .53-.22l1.391-1.39Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,77 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
<svg width="352" height="196" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#a)">
|
||||
<path d="M0 0h352v196H0V0Z" fill="#fff"/>
|
||||
<path d="M0 0h352v196H0V0Z" fill="#E7DFFF"/>
|
||||
<g clip-path="url(#b)" fill="#8F8F9D">
|
||||
<path d="m65.9 130.3.4-1.7c.1-.8 1.3-.8 1.4 0l.4 1.7c0 .3.3.5.6.6l1.7.4c.8.1.8 1.3 0 1.4l-1.7.4c-.3 0-.5.3-.6.6l-.4 1.7c-.1.8-1.3.8-1.4 0l-.4-1.7c0-.3-.3-.5-.6-.6l-1.7-.4c-.8-.1-.8-1.3 0-1.4l1.7-.4c.3 0 .5-.3.6-.6ZM65.9 154.3l.4-1.7c.1-.8 1.3-.8 1.4 0l.4 1.7c0 .3.3.5.6.6l1.7.4c.8.1.8 1.3 0 1.4l-1.7.4c-.3 0-.5.3-.6.6l-.4 1.7c-.1.8-1.3.8-1.4 0l-.4-1.7c0-.3-.3-.5-.6-.6l-1.7-.4c-.8-.1-.8-1.3 0-1.4l1.7-.4c.3 0 .5-.3.6-.6Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M83.5 131.7a2.7 2.7 0 0 0-5 0l-2.5 6.4c-.2.4-.5.7-1 .9l-6.3 2.5a2.7 2.7 0 0 0 0 5l6.4 2.5c.4.2.7.5.9 1l2.5 6.3c.9 2.3 4.1 2.3 5 0l2.5-6.4c.2-.4.5-.7 1-.9l6.3-2.5c2.3-.9 2.3-4.1 0-5l-6.4-2.5c-.4-.2-.7-.5-.9-1l-2.5-6.3Zm-4.7 7.5 2.2-5.7 2.2 5.7c.5 1.2 1.4 2 2.6 2.6l5.7 2.2-5.7 2.2a4.6 4.6 0 0 0-2.6 2.6l-2.2 5.7-2.3-5.7c-.4-1.2-1.3-2-2.5-2.6l-5.7-2.2 5.7-2.3a4.6 4.6 0 0 0 2.6-2.5Z"/>
|
||||
</g>
|
||||
<g clip-path="url(#c)">
|
||||
<path d="M298 110.9a3.1 3.1 0 0 1-2.9-2c-.2-.7-.2-1.3 0-2l1.6-6.4-5.2-4.3a3.1 3.1 0 0 1 0-5l1.8-.6 6.7-.4 2.5-6.3c.5-1.2 1.6-2 3-2 1.2 0 2.4.8 2.8 2l2.5 6.3 6.7.4a3.1 3.1 0 0 1 2.8 4c-.1.6-.5 1.2-1 1.6l-5.1 4.3 1.6 6.5a3.1 3.1 0 0 1-1.2 3.3 3.1 3.1 0 0 1-3.5 0l-5.7-3.5-5.7 3.6c-.5.3-1 .5-1.6.5Zm7.4-26.5a.7.7 0 0 0-.7.4l-2.7 7-1 .7-7.5.5a.7.7 0 0 0-.7.5.7.7 0 0 0 .3.8l5.7 4.8.4 1.2-1.8 7.3a.7.7 0 0 0 .2.7c.2.1.5.3.9 0l6.3-4h1.3l6.3 4a.7.7 0 0 0 .8 0 .7.7 0 0 0 .3-.7l-1.9-7.3.4-1.2 5.8-4.8a.7.7 0 0 0 .2-.8.7.7 0 0 0-.6-.5l-7.5-.5-1-.7-2.8-7a.7.7 0 0 0-.7-.4Z" fill="#8F8F9D"/>
|
||||
</g>
|
||||
<g clip-path="url(#d)">
|
||||
<g clip-path="url(#e)" fill="#8F8F9D">
|
||||
<path d="M66.6 47.4v-2.1L65 43.7h-2.1v-2.6h2c2.4 0 4.3 1.9 4.3 4.2v2.1h-2.6ZM46.6 53.7v2.1l1.6 1.6h2.1V60h-2a4.2 4.2 0 0 1-4.3-4.2v-2.1h2.6ZM65 57.4h-2.1V60h2c2.4 0 4.3-1.9 4.3-4.2v-2.1h-2.6v2.1L65 57.4Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.8 39c0-2.3 1.9-4.2 4.2-4.2h12.6c2.3 0 4.2 1.9 4.2 4.2v6.3c0 2.3-1.9 4.2-4.2 4.2H44a4.2 4.2 0 0 1-4.2-4.2V39Zm12.6-1.6H44c-.9 0-1.6.7-1.6 1.6v6.3c0 .9.7 1.6 1.6 1.6h8.4v-9.5Zm4.2 1.6c.9 0 1.6.7 1.6 1.6v3.1a1.6 1.6 0 0 1-3.2 0v-3.1c0-.9.7-1.6 1.6-1.6Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M44 55.8v-4.2h2.6v4.2c0 .9.7 1.6 1.6 1.6H65c.9 0 1.6-.7 1.6-1.6V45.3c0-.9-.7-1.6-1.6-1.6h-2.1v-2.6h2c2.4 0 4.3 1.9 4.3 4.2v10.5c0 2.3-1.9 4.2-4.2 4.2H68a3.1 3.1 0 0 1 0 6.3h-23a3.1 3.1 0 0 1 0-6.3h3.1a4.2 4.2 0 0 1-4.2-4.2Zm9.5 5.8c0-1 .7-1.6 1.5-1.6h3.2a1.6 1.6 0 0 1 0 3.1H55c-.8 0-1.5-.7-1.5-1.5Z"/>
|
||||
<path d="M46.1 37.4H44L42.4 39v2.1h-2.6v-2c0-2.4 1.9-4.3 4.2-4.3h2.1v2.6ZM42.4 43.2v2.1L44 47h2.1v2.6h-2a4.2 4.2 0 0 1-4.3-4.2v-2h2.6Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g clip-path="url(#f)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 52.3h2a2.7 2.7 0 0 0 2.6-2l.7-2.3 1.3-.7 2.4.6a2.7 2.7 0 0 0 3-1.3l1-1.8c.5-1 .4-2.3-.4-3.1l-1.7-1.9a5.7 5.7 0 0 0 0-1.4l1.7-1.8c.8-.8 1-2.1.3-3.1l-1-1.8a2.7 2.7 0 0 0-3-1.3l-2.3.6-1.3-.7-.7-2.4c-.3-1.1-1.3-1.9-2.5-1.9h-2a2.7 2.7 0 0 0-2.6 2l-.7 2.3-1.3.7-2.4-.6a2.7 2.7 0 0 0-3 1.3l-1 1.8a2.7 2.7 0 0 0 .4 3.1l1.7 1.8a6 6 0 0 0 0 1.4l-1.7 1.9a2.7 2.7 0 0 0-.3 3.1l1 1.8a2.7 2.7 0 0 0 3 1.3l2.3-.6 1.3.7.7 2.4a2.7 2.7 0 0 0 2.5 1.9Zm-3.6-7-1-.1-2.8.7a.6.6 0 0 1-.6-.3l-1-1.8a.6.6 0 0 1 0-.7l2-2.2.3-.8v-1.9l-.2-.9-2-2a.6.6 0 0 1-.2-.8l1-1.8a.6.6 0 0 1 .7-.3l3 .7.8-.2 1.6-1 .6-.6.8-2.8a.6.6 0 0 1 .6-.4h2a.6.6 0 0 1 .7.4l.8 2.8.6.7c.6.2 1.1.5 1.6 1l.9.1 2.9-.7a.6.6 0 0 1 .6.3l1 1.8a.6.6 0 0 1 0 .7l-2 2.1-.3.9V40l.2.8 2 2.2a.6.6 0 0 1 .2.7l-1 1.8a.6.6 0 0 1-.7.3l-2.9-.7-.9.1c-.6.5-1 .8-1.6 1l-.6.7-.8 2.8a.6.6 0 0 1-.6.4h-2a.6.6 0 0 1-.7-.4l-.8-2.8-.6-.7c-.6-.2-1.1-.6-1.6-1Zm7.5-6.2a2.9 2.9 0 1 0-5.7 0 2.9 2.9 0 0 0 5.7 0Zm-6.3-3.4a5 5 0 1 1 7 7 5 5 0 0 1-7-7Z" fill="#8F8F9D"/>
|
||||
</g>
|
||||
<g clip-path="url(#g)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M262.6 160.8a11.5 11.5 0 1 0-23 0 11.5 11.5 0 0 0 23 0ZM245.8 148a13.8 13.8 0 1 1 10.5 25.5 13.8 13.8 0 0 1-10.5-25.5Zm10.2 17a1.2 1.2 0 1 0 1.2-2l-5-2.8v-5.7a1.2 1.2 0 0 0-2-.8c-.2.2-.3.5-.3.8v6.4l.6 1 5.5 3.2Z" fill="#8F8F9D"/>
|
||||
</g>
|
||||
<g filter="url(#h)">
|
||||
<rect x="130" y="52" width="92" height="92" rx="22.4" fill="#fff"/>
|
||||
<g clip-path="url(#i)">
|
||||
<g clip-path="url(#j)" fill="#5B5B66">
|
||||
<path d="M182.1 72h-19V77h18l17.4 17.5L202 91l-18.2-18.2c-.4-.5-1-.8-1.7-.8ZM170.6 98.4l3.5-3.5-3.5-3.4-3.4 3.4 3.4 3.5Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M157 84.2c0-1.3 1-2.4 2.3-2.4H179c.6 0 1.2.2 1.7.7L197 98.9a6.5 6.5 0 0 1 0 9.2l-13.8 13.8a6.5 6.5 0 0 1-9.2 0l-16.4-16.5c-.4-.4-.7-1-.7-1.7V84.2Zm4.8 2.4v16.1l15.7 15.7c.6.6 1.6.6 2.3 0l13.8-13.8c.6-.6.6-1.6 0-2.3l-15.8-15.7h-16Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h352v196H0z"/>
|
||||
</clipPath>
|
||||
<clipPath id="b">
|
||||
<rect x="63" y="128" width="32" height="32" rx="4" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="c">
|
||||
<rect x="290.4" y="81.4" width="30.9" height="30.9" rx="3.9" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="d">
|
||||
<rect x="37.7" y="32.7" width="33.6" height="33.6" rx="4.2" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="e">
|
||||
<path fill="#fff" transform="translate(37.7 32.7)" d="M0 0h33.6v33.6H0z"/>
|
||||
</clipPath>
|
||||
<clipPath id="f">
|
||||
<rect x="235.1" y="26.1" width="26.3" height="26.3" rx="3.3" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="g">
|
||||
<rect x="236.8" y="145.8" width="29.5" height="29.5" rx="3.7" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="i">
|
||||
<rect x="150" y="72" width="51.9" height="51.9" rx="6.5" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="j">
|
||||
<path fill="#fff" transform="translate(150 72)" d="M0 0h51.9v51.9H0z"/>
|
||||
</clipPath>
|
||||
<filter id="h" x="106.6" y="35.9" width="138.9" height="138.9" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="7.3"/>
|
||||
<feGaussianBlur stdDeviation="8.8"/>
|
||||
<feColorMatrix values="0 0 0 0 0.227451 0 0 0 0 0.223529 0 0 0 0 0.266667 0 0 0 0.2 0"/>
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_14063_68005"/>
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_14063_68005" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,77 +0,0 @@
|
||||
<!-- 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 https://mozilla.org/MPL/2.0/. -->
|
||||
<svg width="352" height="214" viewBox="0 0 352 214" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_860_36523)">
|
||||
<rect width="352" height="214" fill="hsla(252, 88%, 70%, 22%)"/>
|
||||
<g filter="url(#filter0_d_860_36523)">
|
||||
<path d="M301.2 27.6845C301.2 22.8882 297.312 19 292.515 19H58.6845C53.8882 19 50 22.8882 50 27.6845V75.1155C50 79.9118 53.8882 83.8 58.6845 83.8H292.515C297.312 83.8 301.2 79.9118 301.2 75.1155V27.6845Z" fill="white"/>
|
||||
</g>
|
||||
<path d="M66.8272 36.3504L64.3962 34.3317C64.2369 34.2002 64.1209 34.0236 64.0636 33.825C64.0063 33.6265 64.0102 33.4153 64.0749 33.219C64.1379 33.0226 64.2588 32.8497 64.4217 32.723C64.5846 32.5964 64.7819 32.5219 64.9878 32.5093L68.1303 32.3061L68.5561 31.9874L69.7155 29.0744C69.8736 28.68 70.2493 28.425 70.6735 28.425C71.0976 28.425 71.4733 28.68 71.6314 29.0736V29.0744L72.7908 31.9874L73.2167 32.3061L76.3591 32.5093C76.7824 32.5365 77.1411 32.8153 77.272 33.219C77.4038 33.6236 77.2771 34.0605 76.9507 34.3317L74.524 36.347L74.371 36.8502L75.1445 39.889C75.249 40.3012 75.0943 40.7271 74.7509 40.9761C74.5842 41.0974 74.3849 41.1658 74.1788 41.1726C73.9727 41.1795 73.7694 41.1243 73.5949 41.0144L70.937 39.3339H70.4091L67.752 41.0144C67.3899 41.2405 66.936 41.2243 66.5952 40.9761C66.4277 40.8557 66.3006 40.6874 66.2304 40.4934C66.1601 40.2994 66.1501 40.0888 66.2016 39.889L66.9734 36.8579L66.8272 36.3504Z" fill="#FFA436"/>
|
||||
<path d="M83.8272 36.3504L81.3962 34.3317C81.2369 34.2002 81.1209 34.0236 81.0636 33.825C81.0063 33.6265 81.0102 33.4153 81.0749 33.219C81.1379 33.0226 81.2588 32.8497 81.4217 32.723C81.5846 32.5964 81.7819 32.5219 81.9878 32.5093L85.1303 32.3061L85.5561 31.9874L86.7155 29.0744C86.8736 28.68 87.2493 28.425 87.6735 28.425C88.0976 28.425 88.4733 28.68 88.6314 29.0736V29.0744L89.7908 31.9874L90.2167 32.3061L93.3591 32.5093C93.7824 32.5365 94.1411 32.8153 94.272 33.219C94.4038 33.6236 94.2771 34.0605 93.9507 34.3317L91.524 36.347L91.371 36.8502L92.1445 39.889C92.249 40.3012 92.0943 40.7271 91.7509 40.9761C91.5842 41.0974 91.3849 41.1658 91.1788 41.1726C90.9727 41.1795 90.7694 41.1243 90.5949 41.0144L87.937 39.3339H87.4091L84.752 41.0144C84.3899 41.2405 83.936 41.2243 83.5952 40.9761C83.4277 40.8557 83.3006 40.6874 83.2304 40.4934C83.1601 40.2994 83.1501 40.0888 83.2016 39.889L83.9734 36.8579L83.8272 36.3504Z" fill="#FFA436"/>
|
||||
<path d="M100.827 36.3504L98.3962 34.3317C98.2369 34.2002 98.1209 34.0236 98.0636 33.825C98.0063 33.6265 98.0102 33.4153 98.0749 33.219C98.1379 33.0226 98.2588 32.8497 98.4217 32.723C98.5846 32.5964 98.7819 32.5219 98.9878 32.5093L102.13 32.3061L102.556 31.9874L103.716 29.0744C103.874 28.68 104.249 28.425 104.673 28.425C105.098 28.425 105.473 28.68 105.631 29.0736V29.0744L106.791 31.9874L107.217 32.3061L110.359 32.5093C110.782 32.5365 111.141 32.8153 111.272 33.219C111.404 33.6236 111.277 34.0605 110.951 34.3317L108.524 36.347L108.371 36.8502L109.144 39.889C109.249 40.3012 109.094 40.7271 108.751 40.9761C108.584 41.0974 108.385 41.1658 108.179 41.1726C107.973 41.1795 107.769 41.1243 107.595 41.0144L104.937 39.3339H104.409L101.752 41.0144C101.39 41.2405 100.936 41.2243 100.595 40.9761C100.428 40.8557 100.301 40.6874 100.23 40.4934C100.16 40.2994 100.15 40.0888 100.202 39.889L100.973 36.8579L100.827 36.3504Z" fill="#FFA436"/>
|
||||
<path d="M117.827 36.3504L115.396 34.3317C115.237 34.2002 115.121 34.0236 115.064 33.825C115.006 33.6265 115.01 33.4153 115.075 33.219C115.138 33.0226 115.259 32.8497 115.422 32.723C115.585 32.5964 115.782 32.5219 115.988 32.5093L119.13 32.3061L119.556 31.9874L120.716 29.0744C120.874 28.68 121.249 28.425 121.673 28.425C122.098 28.425 122.473 28.68 122.631 29.0736V29.0744L123.791 31.9874L124.217 32.3061L127.359 32.5093C127.782 32.5365 128.141 32.8153 128.272 33.219C128.404 33.6236 128.277 34.0605 127.951 34.3317L125.524 36.347L125.371 36.8502L126.144 39.889C126.249 40.3012 126.094 40.7271 125.751 40.9761C125.584 41.0974 125.385 41.1658 125.179 41.1726C124.973 41.1795 124.769 41.1243 124.595 41.0144L121.937 39.3339H121.409L118.752 41.0144C118.39 41.2405 117.936 41.2243 117.595 40.9761C117.428 40.8557 117.301 40.6874 117.23 40.4934C117.16 40.2994 117.15 40.0888 117.202 39.889L117.973 36.8579L117.827 36.3504Z" fill="#FFA436"/>
|
||||
<path d="M135.431 41.1742C135.145 41.1742 134.86 41.0858 134.617 40.9081C134.392 40.746 134.222 40.5196 134.127 40.2587C134.033 39.9979 134.02 39.7147 134.089 39.4461L134.82 36.5774L132.542 34.6853C132.329 34.5091 132.173 34.2724 132.096 34.0062C132.019 33.74 132.024 33.4568 132.111 33.1935C132.196 32.9295 132.358 32.6973 132.577 32.5271C132.796 32.3569 133.061 32.2568 133.338 32.2398L136.292 32.0486L137.387 29.2971C137.598 28.7676 138.103 28.425 138.674 28.425C139.244 28.425 139.749 28.7676 139.96 29.2971L141.055 32.0486L144.009 32.2398C144.286 32.2567 144.551 32.3568 144.77 32.527C144.989 32.6971 145.152 32.9295 145.236 33.1935C145.323 33.4571 145.328 33.7407 145.251 34.0072C145.174 34.2737 145.018 34.5106 144.804 34.687L142.526 36.5782L143.257 39.447C143.326 39.7155 143.313 39.9987 143.219 40.2596C143.125 40.5205 142.954 40.7468 142.729 40.909C142.505 41.0731 142.237 41.1657 141.96 41.1747C141.682 41.1836 141.409 41.1085 141.175 40.9591L138.674 39.3773L136.171 40.9591C135.95 41.0994 135.693 41.174 135.431 41.1742ZM138.674 29.4875C138.609 29.4859 138.545 29.5046 138.491 29.541C138.438 29.5775 138.397 29.6298 138.374 29.6907L137.155 32.7532L136.696 33.0873L133.406 33.3006C133.341 33.3033 133.279 33.326 133.228 33.3658C133.177 33.4056 133.139 33.4604 133.12 33.5225C133.099 33.5835 133.097 33.6497 133.115 33.7117C133.134 33.7738 133.171 33.8286 133.222 33.8684L135.758 35.9756L135.933 36.5153L135.119 39.7096C135.101 39.772 135.104 39.8382 135.126 39.8991C135.148 39.9599 135.189 40.0123 135.242 40.0488C135.296 40.0887 135.438 40.1669 135.604 40.0607L138.39 38.3003H138.958L141.745 40.0607C141.799 40.0969 141.863 40.1152 141.928 40.1131C141.993 40.111 142.055 40.0885 142.107 40.0488C142.16 40.0117 142.2 39.9591 142.222 39.8982C142.243 39.8373 142.246 39.7711 142.228 39.7088L141.414 36.5145L141.589 35.9747L144.125 33.8684C144.176 33.8285 144.214 33.7736 144.232 33.7113C144.25 33.6491 144.248 33.5828 144.227 33.5216C144.208 33.4596 144.17 33.4049 144.119 33.3651C144.068 33.3254 144.006 33.3025 143.941 33.2998L140.652 33.0864L140.192 32.7524L138.973 29.6898C138.95 29.6293 138.909 29.5772 138.855 29.541C138.802 29.5047 138.738 29.4861 138.674 29.4875Z" fill="#FFA436"/>
|
||||
<path d="M151.827 36.3504L149.396 34.3317C149.237 34.2002 149.121 34.0236 149.064 33.825C149.006 33.6265 149.01 33.4153 149.075 33.219C149.138 33.0226 149.259 32.8497 149.422 32.723C149.585 32.5964 149.782 32.5219 149.988 32.5093L153.13 32.3061L153.556 31.9874L154.716 29.0744C154.874 28.68 155.249 28.425 155.673 28.425C156.098 28.425 156.473 28.68 156.631 29.0736V29.0744L157.791 31.9874L158.217 32.3061L161.359 32.5093C161.782 32.5365 162.141 32.8153 162.272 33.219C162.404 33.6236 162.277 34.0605 161.951 34.3317L159.524 36.347L159.371 36.8502L160.144 39.889C160.249 40.3012 160.094 40.7271 159.751 40.9761C159.584 41.0974 159.385 41.1658 159.179 41.1726C158.973 41.1795 158.769 41.1243 158.595 41.0144L155.937 39.3339H155.409L152.752 41.0144C152.39 41.2405 151.936 41.2243 151.595 40.9761C151.428 40.8557 151.301 40.6874 151.23 40.4934C151.16 40.2994 151.15 40.0888 151.202 39.889L151.973 36.8579L151.827 36.3504Z" fill="white"/>
|
||||
<path d="M169.431 41.1742C169.145 41.1742 168.86 41.0858 168.617 40.9081C168.392 40.746 168.222 40.5196 168.127 40.2587C168.033 39.9979 168.02 39.7147 168.089 39.4461L168.82 36.5774L166.542 34.6853C166.329 34.5091 166.173 34.2724 166.096 34.0062C166.019 33.74 166.024 33.4568 166.111 33.1935C166.196 32.9295 166.358 32.6973 166.577 32.5271C166.796 32.3569 167.061 32.2568 167.338 32.2398L170.292 32.0486L171.387 29.2971C171.598 28.7676 172.103 28.425 172.674 28.425C173.244 28.425 173.749 28.7676 173.96 29.2971L175.055 32.0486L178.009 32.2398C178.286 32.2567 178.551 32.3568 178.77 32.527C178.989 32.6971 179.152 32.9295 179.236 33.1935C179.323 33.4571 179.328 33.7407 179.251 34.0072C179.174 34.2737 179.018 34.5106 178.804 34.687L176.526 36.5782L177.257 39.447C177.326 39.7155 177.313 39.9987 177.219 40.2596C177.125 40.5205 176.954 40.7468 176.729 40.909C176.505 41.0731 176.237 41.1657 175.96 41.1747C175.682 41.1836 175.409 41.1085 175.175 40.9591L172.674 39.3773L170.171 40.9591C169.95 41.0994 169.693 41.174 169.431 41.1742ZM172.674 29.4875C172.609 29.4859 172.545 29.5046 172.491 29.541C172.438 29.5775 172.397 29.6298 172.374 29.6907L171.155 32.7532L170.696 33.0873L167.406 33.3006C167.341 33.3033 167.279 33.326 167.228 33.3658C167.177 33.4056 167.139 33.4604 167.12 33.5225C167.099 33.5835 167.097 33.6497 167.115 33.7117C167.134 33.7738 167.171 33.8286 167.222 33.8684L169.758 35.9756L169.933 36.5153L169.119 39.7096C169.101 39.772 169.104 39.8382 169.126 39.8991C169.148 39.9599 169.189 40.0123 169.242 40.0488C169.296 40.0887 169.438 40.1669 169.604 40.0607L172.39 38.3003H172.958L175.745 40.0607C175.799 40.0969 175.863 40.1152 175.928 40.1131C175.993 40.111 176.055 40.0885 176.107 40.0488C176.16 40.0117 176.2 39.9591 176.222 39.8982C176.243 39.8373 176.246 39.7711 176.228 39.7088L175.414 36.5145L175.589 35.9747L178.125 33.8684C178.176 33.8285 178.214 33.7736 178.232 33.7113C178.25 33.6491 178.248 33.5828 178.227 33.5216C178.208 33.4596 178.17 33.4049 178.119 33.3651C178.068 33.3254 178.006 33.3025 177.941 33.2998L174.652 33.0864L174.192 32.7524L172.973 29.6898C172.95 29.6293 172.909 29.5772 172.855 29.541C172.802 29.5047 172.738 29.4861 172.674 29.4875Z" fill="white"/>
|
||||
<rect x="64.7253" y="53.4" width="168" height="4.8" rx="2.4" fill="#E0E0E6"/>
|
||||
<rect x="64.7251" y="67.0001" width="136.8" height="4.8" rx="2.4" fill="#E0E0E6"/>
|
||||
<g filter="url(#filter1_d_860_36523)">
|
||||
<path d="M301.2 102.085C301.2 97.2882 297.312 93.4 292.515 93.4H58.6845C53.8882 93.4 50 97.2882 50 102.085V149.515C50 154.312 53.8882 158.2 58.6845 158.2H292.515C297.312 158.2 301.2 154.312 301.2 149.515V102.085Z" fill="white"/>
|
||||
<path d="M292.515 94.2H58.6845V92.6H292.515V94.2ZM50.8 102.085V149.515H49.2V102.085H50.8ZM58.6845 157.4H292.515V159H58.6845V157.4ZM300.4 149.515V102.085H302V149.515H300.4ZM292.515 157.4C296.87 157.4 300.4 153.87 300.4 149.515H302C302 154.754 297.754 159 292.515 159V157.4ZM50.8 149.515C50.8 153.87 54.33 157.4 58.6845 157.4V159C53.4463 159 49.2 154.754 49.2 149.515H50.8ZM58.6845 94.2C54.33 94.2 50.8 97.7301 50.8 102.085H49.2C49.2 96.8464 53.4463 92.6 58.6845 92.6V94.2ZM292.515 92.6C297.754 92.6 302 96.8464 302 102.085H300.4C300.4 97.73 296.87 94.2 292.515 94.2V92.6Z" fill="#EE0B0B"/>
|
||||
</g>
|
||||
<path d="M66.8272 110.75L64.3962 108.732C64.2369 108.6 64.1209 108.423 64.0636 108.225C64.0063 108.026 64.0102 107.815 64.0749 107.619C64.1379 107.422 64.2588 107.25 64.4217 107.123C64.5846 106.996 64.7819 106.922 64.9878 106.909L68.1303 106.706L68.5561 106.387L69.7155 103.474C69.8736 103.08 70.2493 102.825 70.6735 102.825C71.0976 102.825 71.4733 103.08 71.6314 103.474V103.474L72.7908 106.387L73.2167 106.706L76.3591 106.909C76.7824 106.936 77.1411 107.215 77.272 107.619C77.4038 108.024 77.2771 108.46 76.9507 108.732L74.524 110.747L74.371 111.25L75.1445 114.289C75.249 114.701 75.0943 115.127 74.7509 115.376C74.5842 115.497 74.3849 115.566 74.1788 115.573C73.9727 115.579 73.7694 115.524 73.5949 115.414L70.937 113.734H70.4091L67.752 115.414C67.3899 115.64 66.936 115.624 66.5952 115.376C66.4277 115.256 66.3006 115.087 66.2304 114.893C66.1601 114.699 66.1501 114.489 66.2016 114.289L66.9734 111.258L66.8272 110.75Z" fill="#FFA436"/>
|
||||
<path d="M83.8272 110.75L81.3962 108.732C81.2369 108.6 81.1209 108.423 81.0636 108.225C81.0063 108.026 81.0102 107.815 81.0749 107.619C81.1379 107.422 81.2588 107.25 81.4217 107.123C81.5846 106.996 81.7819 106.922 81.9878 106.909L85.1303 106.706L85.5561 106.387L86.7155 103.474C86.8736 103.08 87.2493 102.825 87.6735 102.825C88.0976 102.825 88.4733 103.08 88.6314 103.474V103.474L89.7908 106.387L90.2167 106.706L93.3591 106.909C93.7824 106.936 94.1411 107.215 94.272 107.619C94.4038 108.024 94.2771 108.46 93.9507 108.732L91.524 110.747L91.371 111.25L92.1445 114.289C92.249 114.701 92.0943 115.127 91.7509 115.376C91.5842 115.497 91.3849 115.566 91.1788 115.573C90.9727 115.579 90.7694 115.524 90.5949 115.414L87.937 113.734H87.4091L84.752 115.414C84.3899 115.64 83.936 115.624 83.5952 115.376C83.4277 115.256 83.3006 115.087 83.2304 114.893C83.1601 114.699 83.1501 114.489 83.2016 114.289L83.9734 111.258L83.8272 110.75Z" fill="#FFA436"/>
|
||||
<path d="M100.827 110.75L98.3962 108.732C98.2369 108.6 98.1209 108.423 98.0636 108.225C98.0063 108.026 98.0102 107.815 98.0749 107.619C98.1379 107.422 98.2588 107.25 98.4217 107.123C98.5846 106.996 98.7819 106.922 98.9878 106.909L102.13 106.706L102.556 106.387L103.716 103.474C103.874 103.08 104.249 102.825 104.673 102.825C105.098 102.825 105.473 103.08 105.631 103.474V103.474L106.791 106.387L107.217 106.706L110.359 106.909C110.782 106.936 111.141 107.215 111.272 107.619C111.404 108.024 111.277 108.46 110.951 108.732L108.524 110.747L108.371 111.25L109.144 114.289C109.249 114.701 109.094 115.127 108.751 115.376C108.584 115.497 108.385 115.566 108.179 115.573C107.973 115.579 107.769 115.524 107.595 115.414L104.937 113.734H104.409L101.752 115.414C101.39 115.64 100.936 115.624 100.595 115.376C100.428 115.256 100.301 115.087 100.23 114.893C100.16 114.699 100.15 114.489 100.202 114.289L100.973 111.258L100.827 110.75Z" fill="#FFA436"/>
|
||||
<path d="M117.827 110.75L115.396 108.732C115.237 108.6 115.121 108.423 115.064 108.225C115.006 108.026 115.01 107.815 115.075 107.619C115.138 107.422 115.259 107.25 115.422 107.123C115.585 106.996 115.782 106.922 115.988 106.909L119.13 106.706L119.556 106.387L120.716 103.474C120.874 103.08 121.249 102.825 121.673 102.825C122.098 102.825 122.473 103.08 122.631 103.474V103.474L123.791 106.387L124.217 106.706L127.359 106.909C127.782 106.936 128.141 107.215 128.272 107.619C128.404 108.024 128.277 108.46 127.951 108.732L125.524 110.747L125.371 111.25L126.144 114.289C126.249 114.701 126.094 115.127 125.751 115.376C125.584 115.497 125.385 115.566 125.179 115.573C124.973 115.579 124.769 115.524 124.595 115.414L121.937 113.734H121.409L118.752 115.414C118.39 115.64 117.936 115.624 117.595 115.376C117.428 115.256 117.301 115.087 117.23 114.893C117.16 114.699 117.15 114.489 117.202 114.289L117.973 111.258L117.827 110.75Z" fill="#FFA436"/>
|
||||
<path d="M134.827 110.75L132.396 108.732C132.237 108.6 132.121 108.423 132.064 108.225C132.006 108.026 132.01 107.815 132.075 107.619C132.138 107.422 132.259 107.25 132.422 107.123C132.585 106.996 132.782 106.922 132.988 106.909L136.13 106.706L136.556 106.387L137.716 103.474C137.874 103.08 138.249 102.825 138.673 102.825C139.098 102.825 139.473 103.08 139.631 103.474V103.474L140.791 106.387L141.217 106.706L144.359 106.909C144.782 106.936 145.141 107.215 145.272 107.619C145.404 108.024 145.277 108.46 144.951 108.732L142.524 110.747L142.371 111.25L143.144 114.289C143.249 114.701 143.094 115.127 142.751 115.376C142.584 115.497 142.385 115.566 142.179 115.573C141.973 115.579 141.769 115.524 141.595 115.414L138.937 113.734H138.409L135.752 115.414C135.39 115.64 134.936 115.624 134.595 115.376C134.428 115.256 134.301 115.087 134.23 114.893C134.16 114.699 134.15 114.489 134.202 114.289L134.973 111.258L134.827 110.75Z" fill="#FFA436"/>
|
||||
<path d="M151.827 110.75L149.396 108.732C149.237 108.6 149.121 108.423 149.064 108.225C149.006 108.026 149.01 107.815 149.075 107.619C149.138 107.422 149.259 107.25 149.422 107.123C149.585 106.996 149.782 106.922 149.988 106.909L153.13 106.706L153.556 106.387L154.716 103.474C154.874 103.08 155.249 102.825 155.673 102.825C156.098 102.825 156.473 103.08 156.631 103.474V103.474L157.791 106.387L158.217 106.706L161.359 106.909C161.782 106.936 162.141 107.215 162.272 107.619C162.404 108.024 162.277 108.46 161.951 108.732L159.524 110.747L159.371 111.25L160.144 114.289C160.249 114.701 160.094 115.127 159.751 115.376C159.584 115.497 159.385 115.566 159.179 115.573C158.973 115.579 158.769 115.524 158.595 115.414L155.937 113.734H155.409L152.752 115.414C152.39 115.64 151.936 115.624 151.595 115.376C151.428 115.256 151.301 115.087 151.23 114.893C151.16 114.699 151.15 114.489 151.202 114.289L151.973 111.258L151.827 110.75Z" fill="white"/>
|
||||
<path d="M169.431 115.574C169.145 115.574 168.86 115.486 168.617 115.308C168.392 115.146 168.222 114.92 168.127 114.659C168.033 114.398 168.02 114.115 168.089 113.846L168.82 110.977L166.542 109.085C166.329 108.909 166.173 108.672 166.096 108.406C166.019 108.14 166.024 107.857 166.111 107.593C166.196 107.329 166.358 107.097 166.577 106.927C166.796 106.757 167.061 106.657 167.338 106.64L170.292 106.449L171.387 103.697C171.598 103.168 172.103 102.825 172.674 102.825C173.244 102.825 173.749 103.168 173.96 103.697L175.055 106.449L178.009 106.64C178.286 106.657 178.551 106.757 178.77 106.927C178.989 107.097 179.152 107.329 179.236 107.593C179.323 107.857 179.328 108.141 179.251 108.407C179.174 108.674 179.018 108.911 178.804 109.087L176.526 110.978L177.257 113.847C177.326 114.115 177.313 114.399 177.219 114.66C177.125 114.92 176.954 115.147 176.729 115.309C176.505 115.473 176.237 115.566 175.96 115.575C175.682 115.584 175.409 115.508 175.175 115.359L172.674 113.777L170.171 115.359C169.95 115.499 169.693 115.574 169.431 115.574ZM172.674 103.887C172.609 103.886 172.545 103.904 172.491 103.941C172.438 103.977 172.397 104.03 172.374 104.091L171.155 107.153L170.696 107.487L167.406 107.701C167.341 107.703 167.279 107.726 167.228 107.766C167.177 107.805 167.139 107.86 167.12 107.922C167.099 107.983 167.097 108.05 167.115 108.112C167.134 108.174 167.171 108.228 167.222 108.268L169.758 110.376L169.933 110.915L169.119 114.11C169.101 114.172 169.104 114.238 169.126 114.299C169.148 114.36 169.189 114.412 169.242 114.449C169.296 114.489 169.438 114.567 169.604 114.461L172.39 112.7H172.958L175.745 114.461C175.799 114.497 175.863 114.515 175.928 114.513C175.993 114.511 176.055 114.488 176.107 114.449C176.16 114.412 176.2 114.359 176.222 114.298C176.243 114.237 176.246 114.171 176.228 114.109L175.414 110.914L175.589 110.375L178.125 108.268C178.176 108.228 178.214 108.173 178.232 108.111C178.25 108.049 178.248 107.983 178.227 107.922C178.208 107.859 178.17 107.805 178.119 107.765C178.068 107.725 178.006 107.702 177.941 107.7L174.652 107.486L174.192 107.152L172.973 104.09C172.95 104.029 172.909 103.977 172.855 103.941C172.802 103.905 172.738 103.886 172.674 103.887Z" fill="white"/>
|
||||
<rect x="64.7253" y="127.8" width="168" height="4.8" rx="2.4" fill="#E0E0E6"/>
|
||||
<rect x="64.7251" y="141.4" width="136.8" height="4.8" rx="2.4" fill="#E0E0E6"/>
|
||||
<g filter="url(#filter2_d_860_36523)">
|
||||
<path d="M301.2 176.485C301.2 171.688 297.312 167.8 292.515 167.8H58.6845C53.8882 167.8 50 171.688 50 176.485V223.916C50 228.712 53.8882 232.6 58.6845 232.6H292.515C297.312 232.6 301.2 228.712 301.2 223.916V176.485Z" fill="white"/>
|
||||
</g>
|
||||
<path d="M66.8272 185.15L64.3962 183.132C64.2369 183 64.1209 182.824 64.0636 182.625C64.0063 182.426 64.0102 182.215 64.0749 182.019C64.1379 181.822 64.2588 181.65 64.4217 181.523C64.5846 181.396 64.7819 181.322 64.9878 181.309L68.1303 181.106L68.5561 180.787L69.7155 177.874C69.8736 177.48 70.2493 177.225 70.6735 177.225C71.0976 177.225 71.4733 177.48 71.6314 177.874V177.874L72.7908 180.787L73.2167 181.106L76.3591 181.309C76.7824 181.336 77.1411 181.615 77.272 182.019C77.4038 182.424 77.2771 182.86 76.9507 183.132L74.524 185.147L74.371 185.65L75.1445 188.689C75.249 189.101 75.0943 189.527 74.7509 189.776C74.5842 189.897 74.3849 189.966 74.1788 189.973C73.9727 189.979 73.7694 189.924 73.5949 189.814L70.937 188.134H70.4091L67.752 189.814C67.3899 190.04 66.936 190.024 66.5952 189.776C66.4277 189.656 66.3006 189.487 66.2304 189.293C66.1601 189.099 66.1501 188.889 66.2016 188.689L66.9734 185.658L66.8272 185.15Z" fill="#FFA436"/>
|
||||
<path d="M83.8272 185.15L81.3962 183.132C81.2369 183 81.1209 182.824 81.0636 182.625C81.0063 182.426 81.0102 182.215 81.0749 182.019C81.1379 181.822 81.2588 181.65 81.4217 181.523C81.5846 181.396 81.7819 181.322 81.9878 181.309L85.1303 181.106L85.5561 180.787L86.7155 177.874C86.8736 177.48 87.2493 177.225 87.6735 177.225C88.0976 177.225 88.4733 177.48 88.6314 177.874V177.874L89.7908 180.787L90.2167 181.106L93.3591 181.309C93.7824 181.336 94.1411 181.615 94.272 182.019C94.4038 182.424 94.2771 182.86 93.9507 183.132L91.524 185.147L91.371 185.65L92.1445 188.689C92.249 189.101 92.0943 189.527 91.7509 189.776C91.5842 189.897 91.3849 189.966 91.1788 189.973C90.9727 189.979 90.7694 189.924 90.5949 189.814L87.937 188.134H87.4091L84.752 189.814C84.3899 190.04 83.936 190.024 83.5952 189.776C83.4277 189.656 83.3006 189.487 83.2304 189.293C83.1601 189.099 83.1501 188.889 83.2016 188.689L83.9734 185.658L83.8272 185.15Z" fill="#FFA436"/>
|
||||
<path d="M100.827 185.15L98.3962 183.132C98.2369 183 98.1209 182.824 98.0636 182.625C98.0063 182.426 98.0102 182.215 98.0749 182.019C98.1379 181.822 98.2588 181.65 98.4217 181.523C98.5846 181.396 98.7819 181.322 98.9878 181.309L102.13 181.106L102.556 180.787L103.716 177.874C103.874 177.48 104.249 177.225 104.673 177.225C105.098 177.225 105.473 177.48 105.631 177.874V177.874L106.791 180.787L107.217 181.106L110.359 181.309C110.782 181.336 111.141 181.615 111.272 182.019C111.404 182.424 111.277 182.86 110.951 183.132L108.524 185.147L108.371 185.65L109.144 188.689C109.249 189.101 109.094 189.527 108.751 189.776C108.584 189.897 108.385 189.966 108.179 189.973C107.973 189.979 107.769 189.924 107.595 189.814L104.937 188.134H104.409L101.752 189.814C101.39 190.04 100.936 190.024 100.595 189.776C100.428 189.656 100.301 189.487 100.23 189.293C100.16 189.099 100.15 188.889 100.202 188.689L100.973 185.658L100.827 185.15Z" fill="#FFA436"/>
|
||||
<path d="M118.431 189.974C118.145 189.974 117.86 189.886 117.617 189.708C117.392 189.546 117.222 189.32 117.127 189.059C117.033 188.798 117.02 188.515 117.089 188.246L117.82 185.377L115.542 183.485C115.329 183.309 115.173 183.072 115.096 182.806C115.019 182.54 115.024 182.257 115.111 181.993C115.196 181.729 115.358 181.497 115.577 181.327C115.796 181.157 116.061 181.057 116.338 181.04L119.292 180.849L120.387 178.097C120.598 177.568 121.103 177.225 121.674 177.225C122.244 177.225 122.749 177.568 122.96 178.097L124.055 180.849L127.009 181.04C127.286 181.057 127.551 181.157 127.77 181.327C127.989 181.497 128.152 181.729 128.236 181.993C128.323 182.257 128.328 182.541 128.251 182.807C128.174 183.074 128.018 183.311 127.804 183.487L125.526 185.378L126.257 188.247C126.326 188.515 126.313 188.799 126.219 189.06C126.125 189.32 125.954 189.547 125.729 189.709C125.505 189.873 125.237 189.966 124.96 189.975C124.682 189.984 124.409 189.908 124.175 189.759L121.674 188.177L119.171 189.759C118.95 189.899 118.693 189.974 118.431 189.974ZM121.674 178.287C121.609 178.286 121.545 178.305 121.491 178.341C121.438 178.377 121.397 178.43 121.374 178.491L120.155 181.553L119.696 181.887L116.406 182.101C116.341 182.103 116.279 182.126 116.228 182.166C116.177 182.206 116.139 182.26 116.12 182.322C116.099 182.383 116.097 182.45 116.115 182.512C116.134 182.574 116.171 182.629 116.222 182.668L118.758 184.776L118.933 185.315L118.119 188.51C118.101 188.572 118.104 188.638 118.126 188.699C118.148 188.76 118.189 188.812 118.242 188.849C118.296 188.889 118.438 188.967 118.604 188.861L121.39 187.1H121.958L124.745 188.861C124.799 188.897 124.863 188.915 124.928 188.913C124.993 188.911 125.055 188.888 125.107 188.849C125.16 188.812 125.2 188.759 125.222 188.698C125.243 188.637 125.246 188.571 125.228 188.509L124.414 185.314L124.589 184.775L127.125 182.668C127.176 182.628 127.214 182.573 127.232 182.511C127.25 182.449 127.248 182.383 127.227 182.322C127.208 182.26 127.17 182.205 127.119 182.165C127.068 182.125 127.006 182.102 126.941 182.1L123.652 181.886L123.192 181.552L121.973 178.49C121.95 178.429 121.909 178.377 121.855 178.341C121.802 178.305 121.738 178.286 121.674 178.287Z" fill="#FFA436"/>
|
||||
<path d="M135.431 189.974C135.145 189.974 134.86 189.886 134.617 189.708C134.392 189.546 134.222 189.32 134.127 189.059C134.033 188.798 134.02 188.515 134.089 188.246L134.82 185.377L132.542 183.485C132.329 183.309 132.173 183.072 132.096 182.806C132.019 182.54 132.024 182.257 132.111 181.993C132.196 181.729 132.358 181.497 132.577 181.327C132.796 181.157 133.061 181.057 133.338 181.04L136.292 180.849L137.387 178.097C137.598 177.568 138.103 177.225 138.674 177.225C139.244 177.225 139.749 177.568 139.96 178.097L141.055 180.849L144.009 181.04C144.286 181.057 144.551 181.157 144.77 181.327C144.989 181.497 145.152 181.729 145.236 181.993C145.323 182.257 145.328 182.541 145.251 182.807C145.174 183.074 145.018 183.311 144.804 183.487L142.526 185.378L143.257 188.247C143.326 188.515 143.313 188.799 143.219 189.06C143.125 189.32 142.954 189.547 142.729 189.709C142.505 189.873 142.237 189.966 141.96 189.975C141.682 189.984 141.409 189.908 141.175 189.759L138.674 188.177L136.171 189.759C135.95 189.899 135.693 189.974 135.431 189.974ZM138.674 178.287C138.609 178.286 138.545 178.305 138.491 178.341C138.438 178.377 138.397 178.43 138.374 178.491L137.155 181.553L136.696 181.887L133.406 182.101C133.341 182.103 133.279 182.126 133.228 182.166C133.177 182.206 133.139 182.26 133.12 182.322C133.099 182.383 133.097 182.45 133.115 182.512C133.134 182.574 133.171 182.629 133.222 182.668L135.758 184.776L135.933 185.315L135.119 188.51C135.101 188.572 135.104 188.638 135.126 188.699C135.148 188.76 135.189 188.812 135.242 188.849C135.296 188.889 135.438 188.967 135.604 188.861L138.39 187.1H138.958L141.745 188.861C141.799 188.897 141.863 188.915 141.928 188.913C141.993 188.911 142.055 188.888 142.107 188.849C142.16 188.812 142.2 188.759 142.222 188.698C142.243 188.637 142.246 188.571 142.228 188.509L141.414 185.314L141.589 184.775L144.125 182.668C144.176 182.628 144.214 182.573 144.232 182.511C144.25 182.449 144.248 182.383 144.227 182.322C144.208 182.26 144.17 182.205 144.119 182.165C144.068 182.125 144.006 182.102 143.941 182.1L140.652 181.886L140.192 181.552L138.973 178.49C138.95 178.429 138.909 178.377 138.855 178.341C138.802 178.305 138.738 178.286 138.674 178.287Z" fill="#FFA436"/>
|
||||
<path d="M151.827 185.15L149.396 183.132C149.237 183 149.121 182.824 149.064 182.625C149.006 182.426 149.01 182.215 149.075 182.019C149.138 181.822 149.259 181.65 149.422 181.523C149.585 181.396 149.782 181.322 149.988 181.309L153.13 181.106L153.556 180.787L154.716 177.874C154.874 177.48 155.249 177.225 155.673 177.225C156.098 177.225 156.473 177.48 156.631 177.874V177.874L157.791 180.787L158.217 181.106L161.359 181.309C161.782 181.336 162.141 181.615 162.272 182.019C162.404 182.424 162.277 182.86 161.951 183.132L159.524 185.147L159.371 185.65L160.144 188.689C160.249 189.101 160.094 189.527 159.751 189.776C159.584 189.897 159.385 189.966 159.179 189.973C158.973 189.979 158.769 189.924 158.595 189.814L155.937 188.134H155.409L152.752 189.814C152.39 190.04 151.936 190.024 151.595 189.776C151.428 189.656 151.301 189.487 151.23 189.293C151.16 189.099 151.15 188.889 151.202 188.689L151.973 185.658L151.827 185.15Z" fill="white"/>
|
||||
<path d="M169.431 189.974C169.145 189.974 168.86 189.886 168.617 189.708C168.392 189.546 168.222 189.32 168.127 189.059C168.033 188.798 168.02 188.515 168.089 188.246L168.82 185.377L166.542 183.485C166.329 183.309 166.173 183.072 166.096 182.806C166.019 182.54 166.024 182.257 166.111 181.993C166.196 181.729 166.358 181.497 166.577 181.327C166.796 181.157 167.061 181.057 167.338 181.04L170.292 180.849L171.387 178.097C171.598 177.568 172.103 177.225 172.674 177.225C173.244 177.225 173.749 177.568 173.96 178.097L175.055 180.849L178.009 181.04C178.286 181.057 178.551 181.157 178.77 181.327C178.989 181.497 179.152 181.729 179.236 181.993C179.323 182.257 179.328 182.541 179.251 182.807C179.174 183.074 179.018 183.311 178.804 183.487L176.526 185.378L177.257 188.247C177.326 188.515 177.313 188.799 177.219 189.06C177.125 189.32 176.954 189.547 176.729 189.709C176.505 189.873 176.237 189.966 175.96 189.975C175.682 189.984 175.409 189.908 175.175 189.759L172.674 188.177L170.171 189.759C169.95 189.899 169.693 189.974 169.431 189.974ZM172.674 178.287C172.609 178.286 172.545 178.305 172.491 178.341C172.438 178.377 172.397 178.43 172.374 178.491L171.155 181.553L170.696 181.887L167.406 182.101C167.341 182.103 167.279 182.126 167.228 182.166C167.177 182.206 167.139 182.26 167.12 182.322C167.099 182.383 167.097 182.45 167.115 182.512C167.134 182.574 167.171 182.629 167.222 182.668L169.758 184.776L169.933 185.315L169.119 188.51C169.101 188.572 169.104 188.638 169.126 188.699C169.148 188.76 169.189 188.812 169.242 188.849C169.296 188.889 169.438 188.967 169.604 188.861L172.39 187.1H172.958L175.745 188.861C175.799 188.897 175.863 188.915 175.928 188.913C175.993 188.911 176.055 188.888 176.107 188.849C176.16 188.812 176.2 188.759 176.222 188.698C176.243 188.637 176.246 188.571 176.228 188.509L175.414 185.314L175.589 184.775L178.125 182.668C178.176 182.628 178.214 182.573 178.232 182.511C178.25 182.449 178.248 182.383 178.227 182.322C178.208 182.26 178.17 182.205 178.119 182.165C178.068 182.125 178.006 182.102 177.941 182.1L174.652 181.886L174.192 181.552L172.973 178.49C172.95 178.429 172.909 178.377 172.855 178.341C172.802 178.305 172.738 178.286 172.674 178.287Z" fill="white"/>
|
||||
<rect x="64.7253" y="202.2" width="168" height="4.8" rx="2.4" fill="#E0E0E6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.4211 126C37.4211 121.357 33.6432 117.579 29 117.579C24.3568 117.579 20.5789 121.357 20.5789 126C20.5789 130.643 24.3568 134.421 29 134.421C33.6432 134.421 37.4211 130.643 37.4211 126ZM19 126C19 120.477 23.4768 116 29 116C34.5232 116 39 120.477 39 126C39 131.523 34.5232 136 29 136C23.4768 136 19 131.523 19 126ZM29.0001 120.737C28.7907 120.737 28.5899 120.82 28.4419 120.968C28.2938 121.116 28.2106 121.317 28.2106 121.526V127.316C28.2106 127.525 28.2938 127.726 28.4419 127.874C28.5899 128.022 28.7907 128.105 29.0001 128.105C29.2095 128.105 29.4103 128.022 29.5584 127.874C29.7064 127.726 29.7896 127.525 29.7896 127.316V121.526C29.7896 121.317 29.7064 121.116 29.5584 120.968C29.4103 120.82 29.2095 120.737 29.0001 120.737ZM28.5264 131.263L28.2106 130.947V130L28.5264 129.684H29.4738L29.7896 130V130.947L29.4738 131.263H28.5264Z" fill="#EE0B0B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_860_36523" x="44.8842" y="15.5895" width="261.432" height="75.0316" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.70526"/>
|
||||
<feGaussianBlur stdDeviation="2.55789"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.227451 0 0 0 0 0.223529 0 0 0 0 0.266667 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_860_36523"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_860_36523" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d_860_36523" x="44.0842" y="89.1894" width="263.032" height="76.6316" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.70526"/>
|
||||
<feGaussianBlur stdDeviation="2.55789"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.227451 0 0 0 0 0.223529 0 0 0 0 0.266667 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_860_36523"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_860_36523" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter2_d_860_36523" x="44.8842" y="164.39" width="261.432" height="75.0316" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1.70526"/>
|
||||
<feGaussianBlur stdDeviation="2.55789"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.227451 0 0 0 0 0.223529 0 0 0 0 0.266667 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_860_36523"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_860_36523" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_860_36523">
|
||||
<rect width="352" height="214" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,6 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
|
||||
<path fill-rule="evenodd" d="M10.5 14c.456.607 1.182 1 2 1 .818 0 1.544-.393 2-1h.25c.69 0 1.25-.56 1.25-1.25V8.777a.75.75 0 0 0-.1-.375l-1.75-3.027A.75.75 0 0 0 13.5 5H11a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v7a2 2 0 0 0 1.443 1.921A2.497 2.497 0 0 0 3.5 15c.818 0 1.544-.393 2-1h5ZM2 4.5a.5.5 0 0 0-.5.5v6A2.5 2.5 0 0 1 6 12.5h3.5V5a.5.5 0 0 0-.5-.5H2ZM14.5 9v-.022L13.067 6.5H11V9h3.5Zm-12 3.5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm9 0a1 1 0 1 1 2 0 1 1 0 0 1-2 0Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 793 B |
@@ -1,6 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
|
||||
<path d="M9.879 0H4v1.5h5.567l5.37 5.393L16 5.835 10.41.22A.75.75 0 0 0 9.88 0ZM6.346 8.116l1.06-1.06-1.06-1.06-1.06 1.06 1.06 1.06Z"/><path d="M2.119 3.75a.75.75 0 0 1 .75-.75h6.01a.75.75 0 0 1 .53.22l5.057 5.056a2 2 0 0 1 0 2.828l-4.243 4.243a2 2 0 0 1-2.828 0l-5.056-5.056a.75.75 0 0 1-.22-.53V3.75Zm1.5.75v4.95l4.836 4.837a.5.5 0 0 0 .708 0l4.243-4.243a.5.5 0 0 0 0-.708L8.568 4.5H3.62Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 733 B |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,65 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
/* Separate logo, label and details into rows and columns */
|
||||
.highlight-item-wrapper {
|
||||
display: grid;
|
||||
column-gap: 1em;
|
||||
grid-template-columns: 1rem auto;
|
||||
grid-template-rows: 1rem auto;
|
||||
}
|
||||
|
||||
.highlight-icon {
|
||||
content: url("chrome://global/skin/icons/defaultFavicon.svg");
|
||||
color: var(--icon-color);
|
||||
fill: currentColor;
|
||||
-moz-context-properties: fill;
|
||||
grid-row-start: 1;
|
||||
grid-column-start: 1;
|
||||
|
||||
&.price {
|
||||
content: url("chrome://browser/skin/price.svg");
|
||||
}
|
||||
|
||||
&.quality {
|
||||
content: url("chrome://browser/content/shopping/assets/quality.svg");
|
||||
}
|
||||
|
||||
&.shipping {
|
||||
content: url("chrome://browser/content/shopping/assets/shipping.svg");
|
||||
}
|
||||
|
||||
&.competitiveness {
|
||||
content: url("chrome://global/skin/icons/trophy.svg");
|
||||
}
|
||||
|
||||
&.packaging\/appearance {
|
||||
content: url("chrome://browser/content/shopping/assets/packaging.svg");
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-label {
|
||||
font-weight: var(--font-weight-bold);
|
||||
grid-column-start: 2;
|
||||
grid-row-start: 1;
|
||||
}
|
||||
|
||||
.highlight-details {
|
||||
grid-column-start: 2;
|
||||
grid-row-start: 2;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.highlight-details-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.highlight-details-list > li {
|
||||
/* Render LTR since English review snippets are only supported at this time. */
|
||||
direction: ltr;
|
||||
margin: 1em 0;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
/**
|
||||
* Class for displaying a list of highlighted product reviews, according to highlight category.
|
||||
*/
|
||||
class Highlight extends MozLitElement {
|
||||
l10nId;
|
||||
highlightType;
|
||||
/**
|
||||
* reviews is a list of Strings, representing all the reviews to display
|
||||
* under a highlight category.
|
||||
*/
|
||||
reviews;
|
||||
|
||||
/**
|
||||
* lang defines the language in which the reviews are written. We should specify
|
||||
* language so that screen readers can read text with the appropriate language packs.
|
||||
*/
|
||||
lang;
|
||||
|
||||
render() {
|
||||
let ulTemplate = [];
|
||||
|
||||
for (let review of this.reviews) {
|
||||
ulTemplate.push(
|
||||
html`<li>
|
||||
<q><span lang=${this.lang}>${review}</span></q>
|
||||
</li>`
|
||||
);
|
||||
}
|
||||
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/highlight-item.css"
|
||||
/>
|
||||
<div class="highlight-item-wrapper">
|
||||
<span class="highlight-icon ${this.highlightType}"></span>
|
||||
<dt class="highlight-label" data-l10n-id=${this.l10nId}></dt>
|
||||
<dd class="highlight-details">
|
||||
<ul class="highlight-details-list">
|
||||
${ulTemplate}
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("highlight-item", Highlight);
|
||||
@@ -1,124 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/highlight-item.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/shopping-card.mjs";
|
||||
|
||||
const VALID_HIGHLIGHT_L10N_IDs = new Map([
|
||||
["price", "shopping-highlight-price"],
|
||||
["quality", "shopping-highlight-quality"],
|
||||
["shipping", "shopping-highlight-shipping"],
|
||||
["competitiveness", "shopping-highlight-competitiveness"],
|
||||
["packaging/appearance", "shopping-highlight-packaging"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Class for displaying all available highlight categories for a product and any
|
||||
* highlight reviews per category.
|
||||
*/
|
||||
class ReviewHighlights extends MozLitElement {
|
||||
/**
|
||||
* highlightsMap is a map of highlight categories to an array of reviews per category.
|
||||
* It is possible for a category to have no reviews.
|
||||
*/
|
||||
#highlightsMap;
|
||||
|
||||
static properties = {
|
||||
highlights: { type: Object },
|
||||
lang: { type: String, reflect: true },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
reviewHighlightsListEl: "#review-highlights-list",
|
||||
};
|
||||
}
|
||||
|
||||
updateHighlightsMap() {
|
||||
let availableKeys;
|
||||
this.#highlightsMap = null;
|
||||
|
||||
try {
|
||||
if (!this.highlights) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter highlights that have data.
|
||||
let keys = Object.keys(this.highlights);
|
||||
availableKeys = keys.filter(
|
||||
key => Object.values(this.highlights[key]).flat().length !== 0
|
||||
);
|
||||
|
||||
// Filter valid highlight category types. Valid types are guaranteed to have data-l10n-ids.
|
||||
availableKeys = availableKeys.filter(key =>
|
||||
VALID_HIGHLIGHT_L10N_IDs.has(key)
|
||||
);
|
||||
|
||||
// If there are no highlights to show in the end, stop here and don't render content.
|
||||
if (!availableKeys.length) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#highlightsMap = new Map();
|
||||
|
||||
for (let key of availableKeys) {
|
||||
// Ignore negative,neutral,positive sentiments and simply append review strings into one array.
|
||||
let reviews = Object.values(this.highlights[key]).flat();
|
||||
this.#highlightsMap.set(key, reviews);
|
||||
}
|
||||
}
|
||||
|
||||
createHighlightElement(type, reviews) {
|
||||
let highlightEl = document.createElement("highlight-item");
|
||||
// We already verify highlight type and its l10n id in updateHighlightsMap.
|
||||
let l10nId = VALID_HIGHLIGHT_L10N_IDs.get(type);
|
||||
highlightEl.id = type;
|
||||
highlightEl.l10nId = l10nId;
|
||||
highlightEl.highlightType = type;
|
||||
highlightEl.reviews = reviews;
|
||||
highlightEl.lang = this.lang;
|
||||
return highlightEl;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.updateHighlightsMap();
|
||||
|
||||
if (!this.#highlightsMap) {
|
||||
this.hidden = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.hidden = false;
|
||||
|
||||
let highlightsTemplate = [];
|
||||
for (let [key, value] of this.#highlightsMap) {
|
||||
let highlightEl = this.createHighlightElement(key, value);
|
||||
highlightsTemplate.push(highlightEl);
|
||||
}
|
||||
|
||||
return html`
|
||||
<shopping-card
|
||||
data-l10n-id="shopping-highlights-label"
|
||||
data-l10n-attrs="label"
|
||||
type="show-more"
|
||||
>
|
||||
<div slot="content" id="review-highlights-wrapper">
|
||||
<dl id="review-highlights-list">${highlightsTemplate}</dl>
|
||||
</div>
|
||||
</shopping-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("review-highlights", ReviewHighlights);
|
||||
@@ -1,132 +0,0 @@
|
||||
/* 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 url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
:host {
|
||||
--background-term-a: #B3FFE3;
|
||||
--background-term-b: #80EBFF;
|
||||
--background-term-c: #FFEA80;
|
||||
--background-term-d: #FFB587;
|
||||
--background-term-f: #FF848B;
|
||||
--in-content-box-border-color: rgba(0, 0, 0, 0.15);
|
||||
--inner-border: 1px solid var(--in-content-box-border-color);
|
||||
--letter-grade-width: 1.5rem;
|
||||
--letter-grade-term-inline-padding: 0.25rem;
|
||||
}
|
||||
|
||||
#letter-grade-wrapper {
|
||||
border-radius: 4px;
|
||||
color: #000;
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
line-height: 150%;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#letter-grade-term {
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
font-size: 1em;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 0.0625rem var(--letter-grade-term-inline-padding);
|
||||
text-align: center;
|
||||
width: var(--letter-grade-width);
|
||||
}
|
||||
|
||||
:host([showdescription]) #letter-grade-term {
|
||||
/* For border "shadow" inside the container */
|
||||
border-block: var(--inner-border);
|
||||
border-inline-start: var(--inner-border);
|
||||
border-start-start-radius: 4px;
|
||||
border-end-start-radius: 4px;
|
||||
/* Add 1px padding so that the letter does not shift when changing
|
||||
* between the show description and no description variants. */
|
||||
padding-inline-end: calc(var(--letter-grade-term-inline-padding) + 1px);
|
||||
}
|
||||
|
||||
:host(:not([showdescription])) #letter-grade-term {
|
||||
border: var(--inner-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#letter-grade-description {
|
||||
/* For border "shadow" inside the container */
|
||||
border-block: var(--inner-border);
|
||||
border-inline-end: var(--inner-border);
|
||||
border-start-end-radius: 4px;
|
||||
border-end-end-radius: 4px;
|
||||
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
font-size: 0.87rem;
|
||||
font-weight: var(--font-weight);
|
||||
margin: 0;
|
||||
padding: 0.125rem 0.5rem;
|
||||
}
|
||||
|
||||
/* Letter grade colors */
|
||||
|
||||
:host([letter="A"]) #letter-grade-term {
|
||||
background-color: var(--background-term-a);
|
||||
}
|
||||
|
||||
:host([letter="A"]) #letter-grade-description {
|
||||
background-color: rgba(231, 255, 246, 1);
|
||||
}
|
||||
|
||||
:host([letter="B"]) #letter-grade-term {
|
||||
background-color: var(--background-term-b);
|
||||
}
|
||||
|
||||
:host([letter="B"]) #letter-grade-description {
|
||||
background-color: rgba(222, 250, 255, 1);
|
||||
}
|
||||
|
||||
:host([letter="C"]) #letter-grade-term {
|
||||
background-color: var(--background-term-c);
|
||||
}
|
||||
|
||||
:host([letter="C"]) #letter-grade-description {
|
||||
background-color: rgba(255, 249, 218, 1);
|
||||
}
|
||||
|
||||
:host([letter="D"]) #letter-grade-term {
|
||||
background-color: var(--background-term-d);
|
||||
}
|
||||
|
||||
:host([letter="D"]) #letter-grade-description {
|
||||
background-color: rgba(252, 230, 213, 1);
|
||||
}
|
||||
|
||||
:host([letter="F"]) #letter-grade-term {
|
||||
background-color: var(--background-term-f);
|
||||
}
|
||||
|
||||
:host([letter="F"]) #letter-grade-description {
|
||||
background-color: rgba(255, 228, 230, 1);
|
||||
}
|
||||
|
||||
@media (prefers-contrast) {
|
||||
:host {
|
||||
--in-content-box-border-color: unset;
|
||||
}
|
||||
|
||||
#letter-grade-term {
|
||||
background-color: var(--in-content-page-color) !important;
|
||||
color: var(--in-content-page-background) !important;
|
||||
}
|
||||
|
||||
#letter-grade-description {
|
||||
background-color: var(--in-content-page-background) !important;
|
||||
color: var(--in-content-page-color) !important;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
const VALID_LETTER_GRADE_L10N_IDS = new Map([
|
||||
["A", "shopping-letter-grade-description-ab"],
|
||||
["B", "shopping-letter-grade-description-ab"],
|
||||
["C", "shopping-letter-grade-description-c"],
|
||||
["D", "shopping-letter-grade-description-df"],
|
||||
["F", "shopping-letter-grade-description-df"],
|
||||
]);
|
||||
|
||||
class LetterGrade extends MozLitElement {
|
||||
#descriptionL10N;
|
||||
|
||||
static properties = {
|
||||
letter: { type: String, reflect: true },
|
||||
showdescription: { type: Boolean, reflect: true },
|
||||
};
|
||||
|
||||
get fluentStrings() {
|
||||
if (!this._fluentStrings) {
|
||||
this._fluentStrings = new Localization(["browser/shopping.ftl"], true);
|
||||
}
|
||||
return this._fluentStrings;
|
||||
}
|
||||
|
||||
descriptionTemplate() {
|
||||
if (this.showdescription) {
|
||||
return html`<p
|
||||
id="letter-grade-description"
|
||||
data-l10n-id=${this.#descriptionL10N}
|
||||
></p>`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
// Do not render if letter is invalid
|
||||
if (!VALID_LETTER_GRADE_L10N_IDS.has(this.letter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.#descriptionL10N = VALID_LETTER_GRADE_L10N_IDS.get(this.letter);
|
||||
let tooltipL10NArgs;
|
||||
// Do not localize tooltip if using Storybook.
|
||||
if (!window.IS_STORYBOOK) {
|
||||
const localizedDescription = this.fluentStrings.formatValueSync(
|
||||
this.#descriptionL10N
|
||||
);
|
||||
tooltipL10NArgs = `{"letter": "${this.letter}", "description": "${localizedDescription}"}`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/letter-grade.css"
|
||||
/>
|
||||
<article
|
||||
id="letter-grade-wrapper"
|
||||
data-l10n-id="shopping-letter-grade-tooltip"
|
||||
data-l10n-attrs="title"
|
||||
data-l10n-args=${tooltipL10NArgs}
|
||||
>
|
||||
<p id="letter-grade-term">${this.letter}</p>
|
||||
${this.descriptionTemplate()}
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("letter-grade", LetterGrade);
|
||||
@@ -1,31 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
const BUNDLE_SRC =
|
||||
"chrome://browser/content/aboutwelcome/aboutwelcome.bundle.js";
|
||||
|
||||
class Onboarding {
|
||||
constructor({ win } = {}) {
|
||||
this.doc = win.document;
|
||||
win.addEventListener("RenderWelcome", () => this._addScriptsAndRender());
|
||||
}
|
||||
|
||||
async _addScriptsAndRender() {
|
||||
// Load the bundle to render the content as configured.
|
||||
this.doc.querySelector(`[src="${BUNDLE_SRC}"]`)?.remove();
|
||||
let bundleScript = this.doc.createElement("script");
|
||||
bundleScript.src = BUNDLE_SRC;
|
||||
this.doc.head.appendChild(bundleScript);
|
||||
}
|
||||
|
||||
static getOnboarding() {
|
||||
if (!this.onboarding) {
|
||||
this.onboarding = new Onboarding({ win: window });
|
||||
}
|
||||
return this.onboarding;
|
||||
}
|
||||
}
|
||||
|
||||
const OnboardingContainer = Onboarding.getOnboarding();
|
||||
export default OnboardingContainer;
|
||||
@@ -1,105 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
:host {
|
||||
--rec-content-gap: var(--space-medium);
|
||||
}
|
||||
|
||||
#recommended-ad-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-small);
|
||||
text-decoration: none;
|
||||
color: var(--in-content-text-color);
|
||||
}
|
||||
|
||||
#recommended-ad-wrapper:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#recommended-ad-wrapper:hover #ad-title {
|
||||
text-decoration: underline;
|
||||
color: var(--link-color-hover);
|
||||
}
|
||||
|
||||
#recommended-ad-wrapper:focus-visible {
|
||||
outline-offset: 4px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
#ad-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--rec-content-gap);
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
#ad-letter-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
gap: var(--rec-content-gap);
|
||||
}
|
||||
|
||||
#ad-preview-image {
|
||||
max-width: 80px;
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
#ad-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: fit-content;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
/* This text won't be localized and when in RTL, the ellipsis is positioned
|
||||
awkwardly so we are forcing LTR */
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
#price {
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
align-self: end;
|
||||
|
||||
&.has-price {
|
||||
justify-content: space-between;
|
||||
align-self: unset;
|
||||
}
|
||||
}
|
||||
|
||||
#sponsored-label {
|
||||
font-size: var(--font-size-small);
|
||||
margin-block-start: 8px;
|
||||
color: var(--text-color-deemphasized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render responsive UI for smaller sidebar widths. CSS variables are not supported
|
||||
* in media queries, so we'll hardcode the units in the calculation.
|
||||
*
|
||||
* 270px: min width for the card component needed before switching to another layout.
|
||||
* 12px: gap between product image, product name, letter grade (see --rec-content-gap). Since we can't use vars, approximate to 12px instead.
|
||||
*/
|
||||
@media (width < calc(270px + 12px)) {
|
||||
#ad-content {
|
||||
flex-direction: column;
|
||||
height: revert;
|
||||
}
|
||||
|
||||
#ad-letter-wrapper {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
#footer {
|
||||
align-self: start;
|
||||
gap: var(--space-small);
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/shopping-card.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://global/content/elements/moz-five-star.mjs";
|
||||
|
||||
const AD_IMPRESSION_TIMEOUT = 1500;
|
||||
// We are only showing prices in USD for now. In the future we will need
|
||||
// to update this to include other currencies.
|
||||
const SUPPORTED_CURRENCY_SYMBOLS = new Map([["USD", "$"]]);
|
||||
|
||||
class RecommendedAd extends MozLitElement {
|
||||
static properties = {
|
||||
product: { type: Object, reflect: true },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
letterGradeEl: "letter-grade",
|
||||
linkEl: "#recommended-ad-wrapper",
|
||||
priceEl: "#price",
|
||||
ratingEl: "moz-five-star",
|
||||
sponsoredLabelEl: "#sponsored-label",
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.initialized = true;
|
||||
|
||||
document.addEventListener("visibilitychange", this);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener("visibilitychange", this);
|
||||
this.resetImpressionTimer();
|
||||
this.revokeImageUrl();
|
||||
}
|
||||
|
||||
startImpressionTimer() {
|
||||
if (!this.timeout && document.visibilityState === "visible") {
|
||||
this.timeout = setTimeout(
|
||||
() => this.recordImpression(),
|
||||
AD_IMPRESSION_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
resetImpressionTimer() {
|
||||
this.timeout = clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
revokeImageUrl() {
|
||||
if (this.imageUrl) {
|
||||
URL.revokeObjectURL(this.imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
recordImpression() {
|
||||
if (this.hasImpressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("AdImpression", {
|
||||
bubbles: true,
|
||||
detail: { aid: this.product.aid, sponsored: this.product.sponsored },
|
||||
})
|
||||
);
|
||||
|
||||
document.removeEventListener("visibilitychange", this);
|
||||
this.resetImpressionTimer();
|
||||
|
||||
this.hasImpressed = true;
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
if (event.button === 0 || event.button === 1) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("AdClicked", {
|
||||
bubbles: true,
|
||||
detail: { aid: this.product.aid, sponsored: this.product.sponsored },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type !== "visibilitychange") {
|
||||
return;
|
||||
}
|
||||
if (document.visibilityState === "visible") {
|
||||
this.startImpressionTimer();
|
||||
} else if (!this.hasImpressed) {
|
||||
this.resetImpressionTimer();
|
||||
}
|
||||
}
|
||||
|
||||
priceTemplate() {
|
||||
const currencySymbol = SUPPORTED_CURRENCY_SYMBOLS.get(
|
||||
this.product.currency
|
||||
);
|
||||
|
||||
// TODO: not all non-USD currencies have the symbol displayed on the right.
|
||||
// Adjust symbol position as we add more supported currencies.
|
||||
let priceLabelText;
|
||||
if (this.product.currency === "USD") {
|
||||
priceLabelText = `${currencySymbol}${this.product.price}`;
|
||||
} else {
|
||||
priceLabelText = `${this.product.price}${currencySymbol}`;
|
||||
}
|
||||
|
||||
return html`<span id="price">${priceLabelText}</span>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.startImpressionTimer();
|
||||
|
||||
this.revokeImageUrl();
|
||||
this.imageUrl = URL.createObjectURL(this.product.image_blob);
|
||||
|
||||
const hasPrice =
|
||||
this.product.price &&
|
||||
SUPPORTED_CURRENCY_SYMBOLS.has(this.product.currency);
|
||||
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/recommended-ad.css"
|
||||
/>
|
||||
<shopping-card
|
||||
data-l10n-id="more-to-consider-ad-label"
|
||||
data-l10n-attrs="label"
|
||||
>
|
||||
<a id="recommended-ad-wrapper" slot="content" href=${
|
||||
this.product.url
|
||||
} target="_blank" title=${this.product.name} @click=${
|
||||
this.handleClick
|
||||
} @auxclick=${this.handleClick}>
|
||||
<div id="ad-content">
|
||||
<img id="ad-preview-image" src=${this.imageUrl}></img>
|
||||
<div id="ad-letter-wrapper">
|
||||
<span id="ad-title" lang="en">${this.product.name}</span>
|
||||
<letter-grade letter=${this.product.grade}></letter-grade>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer" class=${hasPrice ? "has-price" : ""}>
|
||||
${hasPrice ? this.priceTemplate() : null}
|
||||
<moz-five-star rating=${
|
||||
this.product.adjusted_rating
|
||||
}></moz-five-star>
|
||||
</div>
|
||||
</a>
|
||||
</shopping-card>
|
||||
${
|
||||
this.product.sponsored
|
||||
? html`<p
|
||||
id="sponsored-label"
|
||||
data-l10n-id="shopping-sponsored-label"
|
||||
></p>`
|
||||
: null
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("recommended-ad", RecommendedAd);
|
||||
@@ -1,48 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/letter-grade.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/shopping-card.mjs";
|
||||
|
||||
class ReviewReliability extends MozLitElement {
|
||||
static properties = {
|
||||
letter: { type: String },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
letterGradeEl: "letter-grade",
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.letter) {
|
||||
this.hidden = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
return html`
|
||||
<shopping-card
|
||||
data-l10n-id="shopping-review-reliability-label"
|
||||
data-l10n-attrs="label"
|
||||
>
|
||||
<div slot="content">
|
||||
<letter-grade
|
||||
letter=${ifDefined(this.letter)}
|
||||
showdescription
|
||||
></letter-grade>
|
||||
</div>
|
||||
</shopping-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("review-reliability", ReviewReliability);
|
||||
@@ -1,75 +0,0 @@
|
||||
/* 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 url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
#shopping-settings-wrapper {
|
||||
--shopping-settings-between-label-and-control-option-gap: 4px;
|
||||
display: grid;
|
||||
grid-template-rows: auto;
|
||||
row-gap: 8px;
|
||||
margin-top: 12px;
|
||||
|
||||
.shopping-settings-toggle-option-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: var(--shopping-settings-between-label-and-control-option-gap);
|
||||
}
|
||||
|
||||
#shopping-settings-opt-out-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
#shopping-settings-opt-out-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#shopping-settings-toggles-section {
|
||||
row-gap: var(--space-small);
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
#shopping-ads-learn-more-link {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
/* When `browser.shopping.experience2023.autoOpen` is true. */
|
||||
&.shopping-settings-auto-open-ui-enabled {
|
||||
--shopping-settings-between-options-gap: 12px;
|
||||
row-gap: var(--shopping-settings-between-options-gap);
|
||||
|
||||
#shopping-settings-toggles-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: var(--shopping-settings-between-options-gap);
|
||||
}
|
||||
|
||||
.divider {
|
||||
border: var(--shopping-card-border-width) solid var(--shopping-card-border-color);
|
||||
|
||||
@media (prefers-contrast) {
|
||||
border-color: ButtonText;
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
#shopping-settings-opt-out-section {
|
||||
row-gap: var(--shopping-settings-between-label-and-control-option-gap);
|
||||
|
||||
#shopping-settings-opt-out-button {
|
||||
width: fit-content;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#powered-by-fakespot {
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--text-color-deemphasized);
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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/. */
|
||||
|
||||
/* eslint-env mozilla/remote-page */
|
||||
|
||||
import { html } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://global/content/elements/moz-toggle.mjs";
|
||||
|
||||
class ShoppingSettings extends MozLitElement {
|
||||
static properties = {
|
||||
adsEnabled: { type: Boolean },
|
||||
adsEnabledByUser: { type: Boolean },
|
||||
autoOpenEnabled: { type: Boolean },
|
||||
autoOpenEnabledByUser: { type: Boolean },
|
||||
hostname: { type: String },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
wrapperEl: "#shopping-settings-wrapper",
|
||||
recommendationsToggleEl: "#shopping-settings-recommendations-toggle",
|
||||
autoOpenToggleEl: "#shopping-settings-auto-open-toggle",
|
||||
autoOpenToggleDescriptionEl: "#shopping-auto-open-description",
|
||||
dividerEl: ".divider",
|
||||
optOutButtonEl: "#shopping-settings-opt-out-button",
|
||||
shoppingCardEl: "shopping-card",
|
||||
adsLearnMoreLinkEl: "#shopping-ads-learn-more-link",
|
||||
fakespotLearnMoreLinkEl: "#powered-by-fakespot-link",
|
||||
};
|
||||
}
|
||||
|
||||
onToggleRecommendations() {
|
||||
this.adsEnabledByUser = this.recommendationsToggleEl.pressed;
|
||||
let action = this.adsEnabledByUser ? "enabled" : "disabled";
|
||||
Glean.shopping.surfaceAdsSettingToggled.record({ action });
|
||||
RPMSetPref(
|
||||
"browser.shopping.experience2023.ads.userEnabled",
|
||||
this.adsEnabledByUser
|
||||
);
|
||||
}
|
||||
|
||||
onToggleAutoOpen() {
|
||||
this.autoOpenEnabledByUser = this.autoOpenToggleEl.pressed;
|
||||
let action = this.autoOpenEnabledByUser ? "enabled" : "disabled";
|
||||
Glean.shopping.surfaceAutoOpenSettingToggled.record({ action });
|
||||
RPMSetPref(
|
||||
"browser.shopping.experience2023.autoOpen.userEnabled",
|
||||
this.autoOpenEnabledByUser
|
||||
);
|
||||
if (!this.autoOpenEnabledByUser) {
|
||||
RPMSetPref("browser.shopping.experience2023.active", false);
|
||||
}
|
||||
}
|
||||
|
||||
onDisableShopping() {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("DisableShopping", { bubbles: true, composed: true })
|
||||
);
|
||||
Glean.shopping.surfaceOptOutButtonClicked.record();
|
||||
}
|
||||
|
||||
fakespotLinkClicked(e) {
|
||||
if (e.target.localName == "a" && e.button == 0) {
|
||||
Glean.shopping.surfacePoweredByFakespotLinkClicked.record();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Whether we show recommendations at all (including offering a user
|
||||
// control for them) is controlled via a nimbus-enabled pref.
|
||||
let canShowRecommendationToggle = this.adsEnabled;
|
||||
|
||||
let adsToggleMarkup = canShowRecommendationToggle
|
||||
? html` <div class="shopping-settings-toggle-option-wrapper">
|
||||
<moz-toggle
|
||||
id="shopping-settings-recommendations-toggle"
|
||||
?pressed=${this.adsEnabledByUser}
|
||||
data-l10n-id="shopping-settings-recommendations-toggle2"
|
||||
data-l10n-attrs="label"
|
||||
@toggle=${this.onToggleRecommendations}
|
||||
>
|
||||
<span
|
||||
id="shopping-ads-learn-more"
|
||||
data-l10n-id="shopping-settings-recommendations-learn-more3"
|
||||
slot="description"
|
||||
>
|
||||
<a
|
||||
id="shopping-ads-learn-more-link"
|
||||
target="_blank"
|
||||
href="${window.RPMGetFormatURLPref(
|
||||
"app.support.baseURL"
|
||||
)}review-checker-review-quality?utm_campaign=learn-more&utm_medium=inproduct&utm_term=core-sidebar#w_ads_for_relevant_products"
|
||||
data-l10n-name="review-quality-url"
|
||||
></a>
|
||||
</span>
|
||||
</moz-toggle>
|
||||
</div>`
|
||||
: null;
|
||||
|
||||
/* Auto-open experiment changes how the settings card appears by:
|
||||
* 1. Showing a new toggle for enabling/disabling auto-open behaviour
|
||||
* 2. Adding a divider between the toggles and opt-out button
|
||||
* 3. Showing text indicating that Review Checker is enabled (not opted-out) above the opt-out button
|
||||
*
|
||||
* Only show if `browser.shopping.experience2023.autoOpen.enabled` is true.
|
||||
*/
|
||||
let autoOpenDescriptionL10nId =
|
||||
"shopping-settings-auto-open-and-close-description-three-sites";
|
||||
let autoOpenDescriptionL10nArgs = {
|
||||
firstSite: "Amazon",
|
||||
secondSite: "Best Buy",
|
||||
thirdSite: "Walmart",
|
||||
};
|
||||
|
||||
if (
|
||||
RPMGetBoolPref("toolkit.shopping.experience2023.defr", false) &&
|
||||
(this.hostname === "www.amazon.fr" || this.hostname === "www.amazon.de")
|
||||
) {
|
||||
autoOpenDescriptionL10nId =
|
||||
"shopping-settings-auto-open-and-close-description-single-site";
|
||||
autoOpenDescriptionL10nArgs = {
|
||||
currentSite: "Amazon",
|
||||
};
|
||||
}
|
||||
|
||||
let autoOpenToggleMarkup = this.autoOpenEnabled
|
||||
? html` <div class="shopping-settings-toggle-option-wrapper">
|
||||
<moz-toggle
|
||||
id="shopping-settings-auto-open-toggle"
|
||||
?pressed=${this.autoOpenEnabledByUser}
|
||||
data-l10n-id="shopping-settings-auto-open-and-close-toggle"
|
||||
data-l10n-attrs="label"
|
||||
@toggle=${this.onToggleAutoOpen}
|
||||
>
|
||||
<span
|
||||
slot="description"
|
||||
id="shopping-auto-open-description"
|
||||
data-l10n-id=${autoOpenDescriptionL10nId}
|
||||
data-l10n-args=${JSON.stringify(autoOpenDescriptionL10nArgs)}
|
||||
></span>
|
||||
</moz-toggle>
|
||||
</div>`
|
||||
: null;
|
||||
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/settings.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-page.css"
|
||||
/>
|
||||
<shopping-card
|
||||
id="shopping-settings-label"
|
||||
data-l10n-id="shopping-settings-label"
|
||||
data-l10n-attrs="label"
|
||||
type="accordion"
|
||||
>
|
||||
<div
|
||||
id="shopping-settings-wrapper"
|
||||
class=${this.autoOpenEnabled
|
||||
? "shopping-settings-auto-open-ui-enabled"
|
||||
: ""}
|
||||
slot="content"
|
||||
>
|
||||
<section id="shopping-settings-toggles-section">
|
||||
${autoOpenToggleMarkup} ${adsToggleMarkup}
|
||||
</section>
|
||||
${this.autoOpenEnabled
|
||||
? html`<span class="divider" role="separator"></span>`
|
||||
: null}
|
||||
<section id="shopping-settings-opt-out-section">
|
||||
<button
|
||||
class="small-button shopping-button"
|
||||
id="shopping-settings-opt-out-button"
|
||||
data-l10n-id="shopping-settings-opt-out-button"
|
||||
@click=${this.onDisableShopping}
|
||||
></button>
|
||||
</section>
|
||||
</div>
|
||||
</shopping-card>
|
||||
<p
|
||||
id="powered-by-fakespot"
|
||||
data-l10n-id="powered-by-fakespot"
|
||||
@click=${this.fakespotLinkClicked}
|
||||
>
|
||||
<a
|
||||
id="powered-by-fakespot-link"
|
||||
data-l10n-name="fakespot-link"
|
||||
target="_blank"
|
||||
href="https://www.fakespot.com/our-mission?utm_source=review-checker&utm_campaign=fakespot-by-mozilla&utm_medium=inproduct&utm_term=core-sidebar"
|
||||
></a>
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("shopping-settings", ShoppingSettings);
|
||||
@@ -1,60 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
.shopping-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
moz-card {
|
||||
--card-padding: var(--space-medium);
|
||||
|
||||
&::part(heading) {
|
||||
font-size: unset;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
#label-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-small);
|
||||
}
|
||||
|
||||
#heading {
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.show-more footer {
|
||||
width: 100%;
|
||||
background-color: var(--background-color-box);
|
||||
box-shadow: 2px -10px 11px var(--background-color-box);
|
||||
border-top: var(--border-width) solid var(--card-border-color);
|
||||
border-radius: 0 0 var(--border-radius-medium) var(--border-radius-medium);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
padding-block: var(--space-small);
|
||||
inset-inline: 0;
|
||||
}
|
||||
|
||||
.show-more[expanded="false"] {
|
||||
overflow: clip;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
:host(:not([showMoreButtonDisabled])) .show-more ::slotted(div) {
|
||||
margin-block-end: 4rem;
|
||||
}
|
||||
|
||||
:host([showMoreButtonDisabled]) footer {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://global/content/elements/moz-card.mjs";
|
||||
|
||||
const MIN_SHOW_MORE_HEIGHT = 200;
|
||||
/**
|
||||
* A card container to be used in the shopping sidebar. There are three card types.
|
||||
* The default type where no type attribute is required and the card will have no extra functionality.
|
||||
* The "accordion" type will initially not show any content. The card will contain a arrow to expand the
|
||||
* card so all of the content is visible.
|
||||
*
|
||||
* @property {string} label - The label text that will be used for the card header
|
||||
* @property {string} type - (optional) The type of card. No type specified
|
||||
* will be the default card. The other available types are "accordion" and "show-more".
|
||||
*/
|
||||
class ShoppingCard extends MozLitElement {
|
||||
static properties = {
|
||||
label: { type: String },
|
||||
type: { type: String },
|
||||
rating: { type: String },
|
||||
_isExpanded: { type: Boolean },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
contentEl: "#content",
|
||||
};
|
||||
}
|
||||
|
||||
cardTemplate() {
|
||||
if (this.type === "show-more") {
|
||||
return html`
|
||||
<article
|
||||
id="content"
|
||||
class="show-more"
|
||||
aria-describedby="content"
|
||||
expanded="false"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
|
||||
<footer>
|
||||
<moz-button
|
||||
size="small"
|
||||
aria-controls="content"
|
||||
data-l10n-id="shopping-show-more-button"
|
||||
@click=${this.handleShowMoreButtonClick}
|
||||
></moz-button>
|
||||
</footer>
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<div id="content" aria-describedby="content">
|
||||
${this.headingTemplate()}
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
headingTemplate() {
|
||||
if (this.rating) {
|
||||
return html`<div id="label-wrapper">
|
||||
<span id="heading">${this.label}</span>
|
||||
<moz-five-star
|
||||
rating=${this.rating === 0 ? 0.5 : this.rating}
|
||||
></moz-five-star>
|
||||
</div>`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
onCardToggle(e) {
|
||||
const action = e.newState == "open" ? "expanded" : "collapsed";
|
||||
let cardId = this.getAttribute("id");
|
||||
switch (cardId) {
|
||||
case "shopping-settings-label":
|
||||
Glean.shopping.surfaceSettingsExpandClicked.record({ action });
|
||||
break;
|
||||
case "shopping-analysis-explainer-label":
|
||||
Glean.shopping.surfaceShowQualityExplainerClicked.record({
|
||||
action,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleShowMoreButtonClick(e) {
|
||||
this._isExpanded = !this._isExpanded;
|
||||
// toggle show more/show less text
|
||||
e.target.setAttribute(
|
||||
"data-l10n-id",
|
||||
this._isExpanded
|
||||
? "shopping-show-less-button"
|
||||
: "shopping-show-more-button"
|
||||
);
|
||||
// toggle content expanded attribute
|
||||
this.contentEl.attributes.expanded.value = this._isExpanded;
|
||||
|
||||
let action = this._isExpanded ? "expanded" : "collapsed";
|
||||
Glean.shopping.surfaceShowMoreReviewsButtonClicked.record({
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
enableShowMoreButton() {
|
||||
this._isExpanded = false;
|
||||
this.toggleAttribute("showMoreButtonDisabled", false);
|
||||
this.contentEl.attributes.expanded.value = false;
|
||||
}
|
||||
|
||||
disableShowMoreButton() {
|
||||
this._isExpanded = true;
|
||||
this.toggleAttribute("showMoreButtonDisabled", true);
|
||||
this.contentEl.attributes.expanded.value = true;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (this.type !== "show-more") {
|
||||
return;
|
||||
}
|
||||
|
||||
let contentSlot = this.shadowRoot.querySelector("slot[name='content']");
|
||||
let contentSlotEls = contentSlot.assignedElements();
|
||||
if (!contentSlotEls.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let slottedDiv = contentSlotEls[0];
|
||||
|
||||
this.handleContentSlotResize = this.handleContentSlotResize.bind(this);
|
||||
this.contentResizeObserver = new ResizeObserver(
|
||||
this.handleContentSlotResize
|
||||
);
|
||||
this.contentResizeObserver.observe(slottedDiv);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.contentResizeObserver?.disconnect();
|
||||
}
|
||||
|
||||
handleContentSlotResize(entries) {
|
||||
for (let entry of entries) {
|
||||
if (entry.contentRect.height === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.contentRect.height < MIN_SHOW_MORE_HEIGHT) {
|
||||
this.disableShowMoreButton();
|
||||
} else if (this.hasAttribute("showMoreButtonDisabled")) {
|
||||
this.enableShowMoreButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-card.css"
|
||||
/>
|
||||
<moz-card
|
||||
class="shopping-card"
|
||||
type=${this.type}
|
||||
heading=${ifDefined(
|
||||
this.label && !this.rating ? this.label : undefined
|
||||
)}
|
||||
@toggle=${this.onCardToggle}
|
||||
>
|
||||
${this.cardTemplate()}
|
||||
</moz-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define("shopping-card", ShoppingCard);
|
||||
@@ -1,157 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
:host {
|
||||
--shopping-close-button-size: var(--button-min-height);
|
||||
--shopping-header-font-size: 1.3rem;
|
||||
}
|
||||
|
||||
#shopping-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#header-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-small);
|
||||
background-color: var(--shopping-header-background);
|
||||
box-sizing: border-box;
|
||||
padding-block: 16px 8px;
|
||||
padding-inline: 16px 8px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
|
||||
&.header-wrapper-overflow {
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
#shopping-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-small);
|
||||
}
|
||||
|
||||
#shopping-header-title {
|
||||
font-size: var(--shopping-header-font-size);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-wrapper-shadow {
|
||||
box-shadow: 0 1px 2px 0 rgba(58, 57, 68, 0.20);
|
||||
}
|
||||
|
||||
#beta-marker {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight);
|
||||
padding: 2px 4px;
|
||||
margin: 0;
|
||||
line-height: 150%;
|
||||
text-transform: uppercase;
|
||||
color: var(--icon-color);
|
||||
border: 1px solid var(--icon-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
|
||||
#close-button {
|
||||
min-width: var(--shopping-close-button-size);
|
||||
min-height: var(--shopping-close-button-size);
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
background-image: url("chrome://global/skin/icons/close.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin: 0;
|
||||
|
||||
@media not (prefers-contrast) {
|
||||
color: var(--icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
padding: 0 16px 16px;
|
||||
|
||||
&:focus-visible {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-box {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-medium);
|
||||
background: var(--in-content-button-background);
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
.loading-box.small {
|
||||
height: 2.67rem;
|
||||
}
|
||||
|
||||
.loading-box.medium {
|
||||
height: 5.34rem;
|
||||
}
|
||||
|
||||
.loading-box.large {
|
||||
height: 12.8rem;
|
||||
}
|
||||
|
||||
.loading-box:nth-child(odd) {
|
||||
background-color: var(--in-content-button-background);
|
||||
}
|
||||
|
||||
.loading-box:nth-child(even) {
|
||||
background-color: var(--in-content-button-background-hover);
|
||||
}
|
||||
|
||||
@media not (prefers-reduced-motion) {
|
||||
.animate > .loading-box {
|
||||
animation-name: fade-in;
|
||||
animation-direction: alternate;
|
||||
animation-duration: 1s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
/* First box + every 4th box, fifth box + every 4th box */
|
||||
.loading-box:nth-child(4n-3) {
|
||||
animation-delay: -1s;
|
||||
}
|
||||
|
||||
/* Second box + every 4th box, sixth box + every 4th box */
|
||||
.loading-box:nth-child(4n-2) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
/* Third box + every 4th box */
|
||||
.loading-box:nth-child(4n-1) {
|
||||
animation-delay: -1.5s;
|
||||
}
|
||||
|
||||
/* Fourth box + every 4th box */
|
||||
.loading-box:nth-child(4n) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: .25;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,543 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
/* eslint-env mozilla/remote-page */
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/highlights.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/settings.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/adjusted-rating.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/reliability.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/analysis-explainer.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/shopping-message-bar.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/unanalyzed.mjs";
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/recommended-ad.mjs";
|
||||
|
||||
// The number of pixels that must be scrolled from the
|
||||
// top of the sidebar to show the header box shadow.
|
||||
const HEADER_SCROLL_PIXEL_OFFSET = 8;
|
||||
|
||||
const HEADER_NOT_TEXT_WRAPPED_HEIGHT = 32;
|
||||
const SIDEBAR_CLOSED_COUNT_PREF =
|
||||
"browser.shopping.experience2023.sidebarClosedCount";
|
||||
const SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
|
||||
"browser.shopping.experience2023.showKeepSidebarClosedMessage";
|
||||
const SHOPPING_SIDEBAR_ACTIVE_PREF = "browser.shopping.experience2023.active";
|
||||
|
||||
const CLOSED_COUNT_PREVIOUS_MIN = 4;
|
||||
const CLOSED_COUNT_PREVIOUS_MAX = 6;
|
||||
|
||||
export class ShoppingContainer extends MozLitElement {
|
||||
static properties = {
|
||||
data: { type: Object },
|
||||
showOnboarding: { type: Boolean },
|
||||
productUrl: { type: String },
|
||||
recommendationData: { type: Array },
|
||||
isOffline: { type: Boolean },
|
||||
analysisEvent: { type: Object },
|
||||
userReportedAvailable: { type: Boolean },
|
||||
adsEnabled: { type: Boolean },
|
||||
adsEnabledByUser: { type: Boolean },
|
||||
isAnalysisInProgress: { type: Boolean },
|
||||
analysisProgress: { type: Number },
|
||||
showHeaderShadow: { type: Boolean, state: true },
|
||||
isOverflow: { type: Boolean },
|
||||
autoOpenEnabled: { type: Boolean },
|
||||
autoOpenEnabledByUser: { type: Boolean },
|
||||
showingKeepClosedMessage: { type: Boolean },
|
||||
isHeaderOverflow: { type: Boolean, state: true },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
reviewReliabilityEl: "review-reliability",
|
||||
adjustedRatingEl: "adjusted-rating",
|
||||
highlightsEl: "review-highlights",
|
||||
settingsEl: "shopping-settings",
|
||||
analysisExplainerEl: "analysis-explainer",
|
||||
unanalyzedProductEl: "unanalyzed-product-card",
|
||||
shoppingMessageBarEl: "shopping-message-bar",
|
||||
recommendedAdEl: "recommended-ad",
|
||||
loadingEl: "#loading-wrapper",
|
||||
closeButtonEl: "#close-button",
|
||||
keepClosedMessageBarEl: "#keep-closed-message-bar",
|
||||
containerContentEl: "#content",
|
||||
header: "#shopping-header",
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.initialized = true;
|
||||
|
||||
window.document.addEventListener("Update", this);
|
||||
window.document.addEventListener("NewAnalysisRequested", this);
|
||||
window.document.addEventListener("ReanalysisRequested", this);
|
||||
window.document.addEventListener("ReportedProductAvailable", this);
|
||||
window.document.addEventListener("adsEnabledByUserChanged", this);
|
||||
window.document.addEventListener("scroll", this);
|
||||
window.document.addEventListener("UpdateRecommendations", this);
|
||||
window.document.addEventListener("UpdateAnalysisProgress", this);
|
||||
window.document.addEventListener("autoOpenEnabledByUserChanged", this);
|
||||
window.document.addEventListener("ShowKeepClosedMessage", this);
|
||||
window.document.addEventListener("HideKeepClosedMessage", this);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("ContentReady", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.headerResizeObserver?.disconnect();
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.headerResizeObserver = new ResizeObserver(([entry]) =>
|
||||
this.maybeSetIsHeaderOverflow(entry)
|
||||
);
|
||||
this.headerResizeObserver.observe(this.header);
|
||||
}
|
||||
|
||||
updated() {
|
||||
if (this.focusCloseButton) {
|
||||
this.closeButtonEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
async _update({
|
||||
data,
|
||||
showOnboarding,
|
||||
productUrl,
|
||||
recommendationData,
|
||||
adsEnabled,
|
||||
adsEnabledByUser,
|
||||
isAnalysisInProgress,
|
||||
analysisProgress,
|
||||
focusCloseButton,
|
||||
autoOpenEnabled,
|
||||
autoOpenEnabledByUser,
|
||||
}) {
|
||||
// If we're not opted in or there's no shopping URL in the main browser,
|
||||
// the actor will pass `null`, which means this will clear out any existing
|
||||
// content in the sidebar.
|
||||
this.data = data;
|
||||
this.showOnboarding = showOnboarding ?? this.showOnboarding;
|
||||
this.productUrl = productUrl;
|
||||
this.recommendationData = recommendationData;
|
||||
this.isOffline = !navigator.onLine;
|
||||
this.isAnalysisInProgress = isAnalysisInProgress;
|
||||
this.adsEnabled = adsEnabled ?? this.adsEnabled;
|
||||
this.adsEnabledByUser = adsEnabledByUser ?? this.adsEnabledByUser;
|
||||
this.analysisProgress = analysisProgress;
|
||||
this.focusCloseButton = focusCloseButton;
|
||||
this.autoOpenEnabled = autoOpenEnabled ?? this.autoOpenEnabled;
|
||||
this.autoOpenEnabledByUser =
|
||||
autoOpenEnabledByUser ?? this.autoOpenEnabledByUser;
|
||||
}
|
||||
|
||||
_updateRecommendations({ recommendationData }) {
|
||||
this.recommendationData = recommendationData;
|
||||
}
|
||||
|
||||
_updateAnalysisProgress({ progress }) {
|
||||
this.analysisProgress = progress;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "Update":
|
||||
this._update(event.detail);
|
||||
break;
|
||||
case "NewAnalysisRequested":
|
||||
case "ReanalysisRequested":
|
||||
this.isAnalysisInProgress = true;
|
||||
this.analysisEvent = {
|
||||
type: event.type,
|
||||
productUrl: this.productUrl,
|
||||
};
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("PolledRequestMade", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "ReportedProductAvailable":
|
||||
this.userReportedAvailable = true;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("ReportProductAvailable", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
Glean.shopping.surfaceReactivatedButtonClicked.record();
|
||||
break;
|
||||
case "adsEnabledByUserChanged":
|
||||
this.adsEnabledByUser = event.detail?.adsEnabledByUser;
|
||||
break;
|
||||
case "scroll":
|
||||
this.showHeaderShadow = window.scrollY > HEADER_SCROLL_PIXEL_OFFSET;
|
||||
break;
|
||||
case "UpdateRecommendations":
|
||||
this._updateRecommendations(event.detail);
|
||||
break;
|
||||
case "UpdateAnalysisProgress":
|
||||
this._updateAnalysisProgress(event.detail);
|
||||
break;
|
||||
case "autoOpenEnabledByUserChanged":
|
||||
this.autoOpenEnabledByUser = event.detail?.autoOpenEnabledByUser;
|
||||
break;
|
||||
case "ShowKeepClosedMessage":
|
||||
this.showingKeepClosedMessage = true;
|
||||
break;
|
||||
case "HideKeepClosedMessage":
|
||||
this.showingKeepClosedMessage = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
maybeSetIsHeaderOverflow(entry) {
|
||||
let isOverflow = entry.contentRect.height > HEADER_NOT_TEXT_WRAPPED_HEIGHT;
|
||||
if (this.isHeaderOverflow != isOverflow) {
|
||||
this.isHeaderOverflow = isOverflow;
|
||||
}
|
||||
}
|
||||
|
||||
getHostnameFromProductUrl() {
|
||||
let hostname = URL.parse(this.productUrl)?.hostname;
|
||||
if (hostname) {
|
||||
return hostname;
|
||||
}
|
||||
console.warn(`Unknown product url ${this.productUrl}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
analysisDetailsTemplate() {
|
||||
/* At present, en is supported as the default language for reviews. As we support more sites,
|
||||
* update `lang` accordingly if highlights need to be displayed in other languages. */
|
||||
let hostname = this.getHostnameFromProductUrl();
|
||||
|
||||
let lang = "en";
|
||||
let isDEFRSupported = RPMGetBoolPref(
|
||||
"toolkit.shopping.experience2023.defr",
|
||||
false
|
||||
);
|
||||
|
||||
if (isDEFRSupported && hostname === "www.amazon.fr") {
|
||||
lang = "fr";
|
||||
} else if (isDEFRSupported && hostname === "www.amazon.de") {
|
||||
lang = "de";
|
||||
}
|
||||
|
||||
return html`
|
||||
<review-reliability letter=${this.data.grade}></review-reliability>
|
||||
<adjusted-rating
|
||||
rating=${ifDefined(this.data.adjusted_rating)}
|
||||
></adjusted-rating>
|
||||
<review-highlights
|
||||
.highlights=${this.data.highlights}
|
||||
lang=${lang}
|
||||
></review-highlights>
|
||||
`;
|
||||
}
|
||||
|
||||
hasDataTemplate() {
|
||||
let dataBodyTemplate = null;
|
||||
|
||||
// The user requested an analysis which is not done yet.
|
||||
if (
|
||||
this.analysisEvent?.productUrl == this.productUrl &&
|
||||
this.isAnalysisInProgress
|
||||
) {
|
||||
const isReanalysis = this.analysisEvent.type === "ReanalysisRequested";
|
||||
dataBodyTemplate = html`<shopping-message-bar
|
||||
type=${isReanalysis
|
||||
? "reanalysis-in-progress"
|
||||
: "analysis-in-progress"}
|
||||
progress=${this.analysisProgress}
|
||||
></shopping-message-bar>
|
||||
${isReanalysis ? this.analysisDetailsTemplate() : null}`;
|
||||
} else if (this.data?.error) {
|
||||
dataBodyTemplate = html`<shopping-message-bar
|
||||
type="generic-error"
|
||||
></shopping-message-bar>`;
|
||||
} else if (this.data.page_not_supported) {
|
||||
dataBodyTemplate = html`<shopping-message-bar
|
||||
type="page-not-supported"
|
||||
></shopping-message-bar>`;
|
||||
} else if (this.data.deleted_product_reported) {
|
||||
dataBodyTemplate = html`<shopping-message-bar
|
||||
type="product-not-available-reported"
|
||||
></shopping-message-bar>`;
|
||||
} else if (this.data.deleted_product) {
|
||||
dataBodyTemplate = this.userReportedAvailable
|
||||
? html`<shopping-message-bar
|
||||
type="thanks-for-reporting"
|
||||
></shopping-message-bar>`
|
||||
: html`<shopping-message-bar
|
||||
type="product-not-available"
|
||||
></shopping-message-bar>`;
|
||||
} else if (this.data.needs_analysis) {
|
||||
if (!this.data.product_id || typeof this.data.grade != "string") {
|
||||
// Product is new to us.
|
||||
dataBodyTemplate = html`<unanalyzed-product-card
|
||||
productUrl=${ifDefined(this.productUrl)}
|
||||
></unanalyzed-product-card>`;
|
||||
} else {
|
||||
// We successfully analyzed the product before, but the current analysis is outdated and can be updated
|
||||
// via a re-analysis.
|
||||
dataBodyTemplate = html`
|
||||
<shopping-message-bar
|
||||
type="stale"
|
||||
.productUrl=${this.productUrl}
|
||||
></shopping-message-bar>
|
||||
${this.analysisDetailsTemplate()}
|
||||
`;
|
||||
}
|
||||
} else if (this.data.not_enough_reviews) {
|
||||
// We already saw and tried to analyze this product before, but there are not enough reviews
|
||||
// to make a detailed analysis.
|
||||
dataBodyTemplate = html`<shopping-message-bar
|
||||
type="not-enough-reviews"
|
||||
></shopping-message-bar>`;
|
||||
} else {
|
||||
dataBodyTemplate = this.analysisDetailsTemplate();
|
||||
}
|
||||
|
||||
return html`
|
||||
${dataBodyTemplate}${this.explainerTemplate()}${this.recommendationTemplate()}
|
||||
`;
|
||||
}
|
||||
|
||||
recommendationTemplate() {
|
||||
const canShowAds = this.adsEnabled && this.adsEnabledByUser;
|
||||
if (this.recommendationData?.length && canShowAds) {
|
||||
return html`<recommended-ad
|
||||
.product=${this.recommendationData[0]}
|
||||
></recommended-ad>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object?} options
|
||||
* @param {boolean?} options.animate = true
|
||||
* Whether to animate the loading state. Defaults to true.
|
||||
* There will be no animation for users who prefer reduced motion,
|
||||
* irrespective of the value of this option.
|
||||
*/
|
||||
loadingTemplate({ animate = true } = {}) {
|
||||
/* Due to limitations with aria-busy for certain screen readers
|
||||
* (see Bug 1682063), mark loading container as a pseudo image and
|
||||
* use aria-label as a workaround. */
|
||||
return html`
|
||||
<div
|
||||
id="loading-wrapper"
|
||||
data-l10n-id="shopping-a11y-loading"
|
||||
role="img"
|
||||
class=${animate ? "animate" : ""}
|
||||
>
|
||||
<div class="loading-box medium"></div>
|
||||
<div class="loading-box medium"></div>
|
||||
<div class="loading-box large"></div>
|
||||
<div class="loading-box small"></div>
|
||||
<div class="loading-box large"></div>
|
||||
<div class="loading-box small"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
noDataTemplate({ animate = true } = {}) {
|
||||
if (this.isAnalysisInProgress) {
|
||||
return html`<shopping-message-bar
|
||||
type="analysis-in-progress"
|
||||
progress=${this.analysisProgress}
|
||||
></shopping-message-bar
|
||||
>${this.explainerTemplate()}${this.recommendationTemplate()}`;
|
||||
}
|
||||
return this.loadingTemplate({ animate });
|
||||
}
|
||||
|
||||
headerTemplate() {
|
||||
const headerWrapperClasses = `${this.showHeaderShadow ? "header-wrapper-shadow" : ""} ${this.isHeaderOverflow ? "header-wrapper-overflow" : ""}`;
|
||||
return html`<div id="header-wrapper" class=${headerWrapperClasses}>
|
||||
<header id="shopping-header" data-l10n-id="shopping-a11y-header">
|
||||
<h1
|
||||
id="shopping-header-title"
|
||||
data-l10n-id="shopping-main-container-title"
|
||||
></h1>
|
||||
<p id="beta-marker" data-l10n-id="shopping-beta-marker"></p>
|
||||
</header>
|
||||
<button
|
||||
id="close-button"
|
||||
class="ghost-button shopping-button"
|
||||
data-l10n-id="shopping-close-button"
|
||||
@click=${this.handleCloseButtonClick}
|
||||
></button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// TODO: (Bug 1949647) do not render "Keep closed" message and notification card simultaneously.
|
||||
renderContainer(sidebarContent, { showSettings = false } = {}) {
|
||||
return html`<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-container.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://global/skin/in-content/common.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-page.css"
|
||||
/>
|
||||
<div id="shopping-container">
|
||||
${this.headerTemplate()}
|
||||
<div id="content" aria-live="polite" aria-busy=${!this.data}>
|
||||
<slot name="multi-stage-message-slot"></slot>
|
||||
${this.userInteractionMessageTemplate()}${sidebarContent}
|
||||
${showSettings
|
||||
? this.settingsTemplate({ className: "first-footer-card" })
|
||||
: null}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
explainerTemplate({ className = "" } = {}) {
|
||||
return html`<analysis-explainer
|
||||
productUrl=${ifDefined(this.productUrl)}
|
||||
class=${className}
|
||||
></analysis-explainer>`;
|
||||
}
|
||||
|
||||
settingsTemplate({ className = "" } = {}) {
|
||||
let hostname = this.getHostnameFromProductUrl();
|
||||
return html` <shopping-settings
|
||||
?adsEnabled=${this.adsEnabled}
|
||||
?adsEnabledByUser=${this.adsEnabledByUser}
|
||||
?autoOpenEnabled=${this.autoOpenEnabled}
|
||||
?autoOpenEnabledByUser=${this.autoOpenEnabledByUser}
|
||||
.hostname=${hostname}
|
||||
class=${className}
|
||||
></shopping-settings>`;
|
||||
}
|
||||
|
||||
userInteractionMessageTemplate() {
|
||||
if (!this.autoOpenEnabled || !this.autoOpenEnabledByUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.showingKeepClosedMessage) {
|
||||
return this.keepClosedMessageTemplate();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
keepClosedMessageTemplate() {
|
||||
return html`<shopping-message-bar
|
||||
id="keep-closed-message-bar"
|
||||
type="keep-closed"
|
||||
></shopping-message-bar>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
// this.data may be null because we're viewing a non PDP, or a PDP that does not have data yet.
|
||||
const isLoadingData = !this.data;
|
||||
|
||||
if (this.showOnboarding) {
|
||||
content = html``;
|
||||
} else if (isLoadingData || this.isOffline) {
|
||||
content = this.noDataTemplate({ animate: !this.isOffline });
|
||||
} else {
|
||||
content = this.hasDataTemplate();
|
||||
}
|
||||
|
||||
const showSettings =
|
||||
!this.showOnboarding &&
|
||||
!this.isOffline &&
|
||||
(!isLoadingData || (isLoadingData && this.isAnalysisInProgress));
|
||||
return this.renderContainer(content, { showSettings });
|
||||
}
|
||||
|
||||
handleCloseButtonClick() {
|
||||
let canShowKeepClosedMessage;
|
||||
|
||||
if (this.autoOpenEnabled && this.autoOpenEnabledByUser) {
|
||||
canShowKeepClosedMessage =
|
||||
this._canShowKeepClosedMessageOnCloseButtonClick();
|
||||
}
|
||||
|
||||
if (!canShowKeepClosedMessage) {
|
||||
RPMSetPref(SHOPPING_SIDEBAR_ACTIVE_PREF, false);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("CloseShoppingSidebar", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Glean.shopping.surfaceClosed.record({ source: "closeButton" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delaying close is only applicable to the "Keep closed" message.
|
||||
*
|
||||
* We can show the message under these conditions:
|
||||
* - User has already seen the new location notification card
|
||||
* - The message was never seen before
|
||||
* - User clicked the close button at least 5 times in a session (i.e. met the minimum of 4 counts before this point)
|
||||
* - The number of close attempts in a session is less than 7 (i.e. met the maximum of 6 counts before this point)
|
||||
*
|
||||
* Do not show the message again after the 7th close.
|
||||
*/
|
||||
_canShowKeepClosedMessageOnCloseButtonClick() {
|
||||
if (
|
||||
!RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false) ||
|
||||
this.showOnboarding
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let sidebarClosedCount = RPMGetIntPref(SIDEBAR_CLOSED_COUNT_PREF, 0);
|
||||
let canShowKeepClosedMessage =
|
||||
!this.showingKeepClosedMessage &&
|
||||
sidebarClosedCount >= CLOSED_COUNT_PREVIOUS_MIN;
|
||||
|
||||
if (canShowKeepClosedMessage) {
|
||||
this.showingKeepClosedMessage = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
this.showingKeepClosedMessage = false;
|
||||
|
||||
if (sidebarClosedCount >= CLOSED_COUNT_PREVIOUS_MAX) {
|
||||
RPMSetPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false);
|
||||
} else {
|
||||
RPMSetPref(SIDEBAR_CLOSED_COUNT_PREF, sidebarClosedCount + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("shopping-container", ShoppingContainer);
|
||||
@@ -1,70 +0,0 @@
|
||||
/* 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 url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#message-bar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:host([type="stale"]) #message-bar-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 8px;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.shopping-message-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-block: 0.5rem;
|
||||
gap: 0.75rem;
|
||||
|
||||
&.analysis-in-progress {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.icon {
|
||||
--message-bar-icon-url: url("chrome://global/skin/icons/info-filled.svg");
|
||||
width: var(--icon-size-default);
|
||||
height: var(--icon-size-default);
|
||||
flex-shrink: 0;
|
||||
appearance: none;
|
||||
-moz-context-properties: fill, stroke;
|
||||
fill: currentColor;
|
||||
stroke: currentColor;
|
||||
color: var(--icon-color);
|
||||
background-image: var(--message-bar-icon-url);
|
||||
}
|
||||
}
|
||||
|
||||
:host([type=analysis-in-progress]) .icon,
|
||||
:host([type=reanalysis-in-progress]) .icon {
|
||||
--message-bar-icon-url: conic-gradient(var(--icon-color-information) var(--analysis-progress-pcent, 0%), transparent var(--analysis-progress-pcent, 0%));
|
||||
border: 1px solid var(--icon-color);
|
||||
border-radius: 50%;
|
||||
margin-block: 1px 0;
|
||||
margin-inline: 1px 0;
|
||||
width: calc(var(--icon-size-default) - 2px);
|
||||
height: calc(var(--icon-size-default) - 2px);
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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/. */
|
||||
|
||||
/* eslint-env mozilla/remote-page */
|
||||
|
||||
import { html, styleMap } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://global/content/elements/moz-message-bar.mjs";
|
||||
|
||||
const SHOPPING_SIDEBAR_ACTIVE_PREF = "browser.shopping.experience2023.active";
|
||||
const SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
|
||||
"browser.shopping.experience2023.showKeepSidebarClosedMessage";
|
||||
const SHOPPING_AUTO_OPEN_SIDEBAR_PREF =
|
||||
"browser.shopping.experience2023.autoOpen.userEnabled";
|
||||
|
||||
class ShoppingMessageBar extends MozLitElement {
|
||||
#MESSAGE_TYPES_RENDER_TEMPLATE_MAPPING = new Map([
|
||||
["stale", () => this.staleWarningTemplate()],
|
||||
["generic-error", () => this.genericErrorTemplate()],
|
||||
["not-enough-reviews", () => this.notEnoughReviewsTemplate()],
|
||||
["product-not-available", () => this.productNotAvailableTemplate()],
|
||||
["thanks-for-reporting", () => this.thanksForReportingTemplate()],
|
||||
[
|
||||
"product-not-available-reported",
|
||||
() => this.productNotAvailableReportedTemplate(),
|
||||
],
|
||||
["analysis-in-progress", () => this.analysisInProgressTemplate()],
|
||||
["reanalysis-in-progress", () => this.reanalysisInProgressTemplate()],
|
||||
["page-not-supported", () => this.pageNotSupportedTemplate()],
|
||||
["thank-you-for-feedback", () => this.thankYouForFeedbackTemplate()],
|
||||
["keep-closed", () => this.keepClosedTemplate()],
|
||||
]);
|
||||
|
||||
static properties = {
|
||||
type: { type: String },
|
||||
productUrl: { type: String, reflect: true },
|
||||
progress: { type: Number, reflect: true },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
reAnalysisButtonEl: "#message-bar-reanalysis-button",
|
||||
productAvailableBtnEl: "#message-bar-report-product-available-btn",
|
||||
yesKeepClosedButtonEl: "#yes-keep-closed-button",
|
||||
noThanksButtonEl: "#no-thanks-button",
|
||||
};
|
||||
}
|
||||
|
||||
onClickAnalysisButton() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("ReanalysisRequested", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
Glean.shopping.surfaceReanalyzeClicked.record();
|
||||
}
|
||||
|
||||
onClickProductAvailable() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("ReportedProductAvailable", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
handleNoThanksClick() {
|
||||
RPMSetPref(SHOPPING_SIDEBAR_ACTIVE_PREF, false);
|
||||
RPMSetPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("HideKeepClosedMessage", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
Glean.shopping.surfaceNoThanksButtonClicked.record();
|
||||
}
|
||||
|
||||
handleKeepClosedClick() {
|
||||
RPMSetPref(SHOPPING_SIDEBAR_ACTIVE_PREF, false);
|
||||
RPMSetPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false);
|
||||
RPMSetPref(SHOPPING_AUTO_OPEN_SIDEBAR_PREF, false);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("HideKeepClosedMessage", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
Glean.shopping.surfaceYesKeepClosedButtonClicked.record();
|
||||
}
|
||||
|
||||
staleWarningTemplate() {
|
||||
return html`<div class="shopping-message-bar">
|
||||
<span class="icon"></span>
|
||||
<article id="message-bar-container" aria-labelledby="header">
|
||||
<span
|
||||
data-l10n-id="shopping-message-bar-warning-stale-analysis-message-2"
|
||||
></span>
|
||||
<button
|
||||
id="message-bar-reanalysis-button"
|
||||
class="small-button shopping-button"
|
||||
data-l10n-id="shopping-message-bar-warning-stale-analysis-button"
|
||||
@click=${this.onClickAnalysisButton}
|
||||
></button>
|
||||
</article>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
genericErrorTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="warning"
|
||||
data-l10n-id="shopping-message-bar-generic-error"
|
||||
>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
notEnoughReviewsTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="warning"
|
||||
data-l10n-id="shopping-message-bar-warning-not-enough-reviews"
|
||||
>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
productNotAvailableTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="warning"
|
||||
data-l10n-id="shopping-message-bar-warning-product-not-available"
|
||||
>
|
||||
<button
|
||||
slot="actions"
|
||||
id="message-bar-report-product-available-btn"
|
||||
class="small-button shopping-button"
|
||||
data-l10n-id="shopping-message-bar-warning-product-not-available-button2"
|
||||
@click=${this.onClickProductAvailable}
|
||||
></button>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
thanksForReportingTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="info"
|
||||
data-l10n-id="shopping-message-bar-thanks-for-reporting"
|
||||
>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
productNotAvailableReportedTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="warning"
|
||||
data-l10n-id="shopping-message-bar-warning-product-not-available-reported"
|
||||
>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
analysisInProgressTemplate() {
|
||||
return html`<div
|
||||
class="shopping-message-bar analysis-in-progress"
|
||||
style=${styleMap({
|
||||
"--analysis-progress-pcent": `${this.progress}%`,
|
||||
})}
|
||||
>
|
||||
<span class="icon"></span>
|
||||
<article
|
||||
id="message-bar-container"
|
||||
aria-labelledby="header"
|
||||
type="analysis"
|
||||
>
|
||||
<strong
|
||||
id="header"
|
||||
data-l10n-id="shopping-message-bar-analysis-in-progress-with-amount"
|
||||
data-l10n-args=${JSON.stringify({
|
||||
percentage: Math.round(this.progress),
|
||||
})}
|
||||
></strong>
|
||||
<span
|
||||
data-l10n-id="shopping-message-bar-analysis-in-progress-message2"
|
||||
></span>
|
||||
</article>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
reanalysisInProgressTemplate() {
|
||||
return html`<div
|
||||
class="shopping-message-bar"
|
||||
id="reanalysis-in-progress-message"
|
||||
style=${styleMap({
|
||||
"--analysis-progress-pcent": `${this.progress}%`,
|
||||
})}
|
||||
>
|
||||
<span class="icon"></span>
|
||||
<article
|
||||
id="message-bar-container"
|
||||
aria-labelledby="header"
|
||||
type="re-analysis"
|
||||
>
|
||||
<span
|
||||
id="header"
|
||||
data-l10n-id="shopping-message-bar-analysis-in-progress-with-amount"
|
||||
data-l10n-args=${JSON.stringify({
|
||||
percentage: Math.round(this.progress),
|
||||
})}
|
||||
></span>
|
||||
</article>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
pageNotSupportedTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="warning"
|
||||
data-l10n-id="shopping-message-bar-page-not-supported"
|
||||
>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
thankYouForFeedbackTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="success"
|
||||
dismissable
|
||||
data-l10n-id="shopping-survey-thanks"
|
||||
>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
keepClosedTemplate() {
|
||||
return html`<moz-message-bar
|
||||
type="info"
|
||||
data-l10n-id="shopping-message-bar-keep-closed-header"
|
||||
>
|
||||
<moz-button-group slot="actions">
|
||||
<button
|
||||
id="no-thanks-button"
|
||||
class="small-button shopping-button"
|
||||
data-l10n-id="shopping-message-bar-keep-closed-dismiss-button"
|
||||
@click=${this.handleNoThanksClick}
|
||||
></button>
|
||||
<button
|
||||
id="yes-keep-closed-button"
|
||||
class="primary small-button shopping-button"
|
||||
data-l10n-id="shopping-message-bar-keep-closed-accept-button"
|
||||
@click=${this.handleKeepClosedClick}
|
||||
></button>
|
||||
</moz-button-group>
|
||||
</moz-message-bar>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let messageBarTemplate = this.#MESSAGE_TYPES_RENDER_TEMPLATE_MAPPING.get(
|
||||
this.type
|
||||
)();
|
||||
if (messageBarTemplate) {
|
||||
if (this.type == "stale") {
|
||||
Glean.shopping.surfaceStaleAnalysisShown.record();
|
||||
}
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-message-bar.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-page.css"
|
||||
/>
|
||||
${messageBarTemplate}
|
||||
`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("shopping-message-bar", ShoppingMessageBar);
|
||||
@@ -1,28 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
:root {
|
||||
--shopping-header-background: light-dark(#f9f9fb, #2b2a33);
|
||||
background-color: var(--shopping-header-background);
|
||||
font: menu;
|
||||
}
|
||||
|
||||
@media (prefers-contrast) {
|
||||
button.shopping-button:enabled,
|
||||
button.ghost-button:not(.semi-transparent):enabled {
|
||||
background-color: ButtonFace;
|
||||
border-color: ButtonText;
|
||||
color: ButtonText;
|
||||
|
||||
&:hover {
|
||||
background-color: SelectedItemText;
|
||||
border-color: SelectedItem;
|
||||
color: SelectedItem;
|
||||
}
|
||||
|
||||
&:hover:active {
|
||||
border-color: ButtonText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// This is loaded into chrome windows with the subscript loader. Wrap in
|
||||
// a block to prevent accidentally leaking globals onto `window`.
|
||||
{
|
||||
const SHOPPING_SIDEBAR_WIDTH_PREF =
|
||||
"browser.shopping.experience2023.sidebarWidth";
|
||||
class ShoppingSidebar extends MozXULElement {
|
||||
#initialized;
|
||||
|
||||
static get markup() {
|
||||
return `
|
||||
<browser
|
||||
class="shopping-sidebar"
|
||||
autoscroll="false"
|
||||
disablefullscreen="true"
|
||||
disablehistory="true"
|
||||
flex="1"
|
||||
message="true"
|
||||
manualactiveness="true"
|
||||
remoteType="privilegedabout"
|
||||
maychangeremoteness="true"
|
||||
remote="true"
|
||||
src="about:shoppingsidebar"
|
||||
type="content"
|
||||
messagemanagergroup="shopping-sidebar"
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (this.#initialized) {
|
||||
return;
|
||||
}
|
||||
this.resizeObserverFn = this.resizeObserverFn.bind(this);
|
||||
this.appendChild(this.constructor.fragment);
|
||||
|
||||
let previousWidth = Services.prefs.getIntPref(
|
||||
SHOPPING_SIDEBAR_WIDTH_PREF,
|
||||
0
|
||||
);
|
||||
if (previousWidth > 0) {
|
||||
this.style.width = `${previousWidth}px`;
|
||||
}
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.resizeObserverFn);
|
||||
this.resizeObserver.observe(this);
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
resizeObserverFn(entries) {
|
||||
for (let entry of entries) {
|
||||
if (entry.contentBoxSize[0].inlineSize < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.prefs.setIntPref(
|
||||
SHOPPING_SIDEBAR_WIDTH_PREF,
|
||||
entry.contentBoxSize[0].inlineSize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("shopping-sidebar", ShoppingSidebar);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
<!doctype html>
|
||||
<html
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
role="document"
|
||||
id="shopping"
|
||||
class="system-font-size"
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src resource: chrome:; object-src 'none'; img-src blob: chrome:;"
|
||||
/>
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<link rel="localization" href="branding/brand.ftl" />
|
||||
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
||||
<link rel="localization" href="toolkit/global/mozFiveStar.ftl" />
|
||||
<link rel="localization" href="toolkit/global/mozSupportLink.ftl" />
|
||||
<link rel="localization" href="toolkit/global/notification.ftl" />
|
||||
<link rel="localization" href="preview/shopping.ftl" />
|
||||
<link rel="localization" href="browser/shopping.ftl" />
|
||||
<link rel="localization" href="toolkit/global/mozMessageBar.ftl" />
|
||||
|
||||
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/shopping-page.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/aboutwelcome/aboutwelcome.css"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="chrome://browser/content/aboutwelcome/aboutwelcome.bundle.js"
|
||||
/>
|
||||
|
||||
<script
|
||||
type="module"
|
||||
src="chrome://browser/content/shopping/onboarding.mjs"
|
||||
></script>
|
||||
<script
|
||||
type="module"
|
||||
src="chrome://browser/content/shopping/shopping-container.mjs"
|
||||
></script>
|
||||
<title data-l10n-id="shopping-page-title"></title>
|
||||
</head>
|
||||
<body>
|
||||
<shopping-container>
|
||||
<div
|
||||
id="multi-stage-message-root"
|
||||
class="onboardingContainer shopping"
|
||||
slot="multi-stage-message-slot"
|
||||
></div>
|
||||
<script src="chrome://global/content/vendor/react.js"></script>
|
||||
<script src="chrome://global/content/vendor/react-dom.js"></script>
|
||||
</shopping-container>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,41 +0,0 @@
|
||||
/* 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 url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
#unanalyzed-product-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#unanalyzed-product-icon {
|
||||
max-width: 264px;
|
||||
max-height: 290px;
|
||||
width: 100%;
|
||||
content: url("chrome://browser/content/shopping/assets/unanalyzedLight.avif");
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
content: url("chrome://browser/content/shopping/assets/unanalyzedDark.avif");
|
||||
}
|
||||
}
|
||||
|
||||
#unanalyzed-product-message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.5;
|
||||
|
||||
> h2 {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
> p {
|
||||
margin-block: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
#unanalyzed-product-analysis-button {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
* 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 } from "chrome://global/content/vendor/lit.all.mjs";
|
||||
|
||||
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import "chrome://browser/content/shopping/shopping-card.mjs";
|
||||
|
||||
class UnanalyzedProductCard extends MozLitElement {
|
||||
static properties = {
|
||||
productURL: { type: String, reflect: true },
|
||||
};
|
||||
|
||||
static get queries() {
|
||||
return {
|
||||
analysisButtonEl: "#unanalyzed-product-analysis-button",
|
||||
};
|
||||
}
|
||||
|
||||
onClickAnalysisButton() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("NewAnalysisRequested", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
Glean.shopping.surfaceAnalyzeReviewsNoneAvailableClicked.record();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="chrome://browser/content/shopping/unanalyzed.css"
|
||||
/>
|
||||
<shopping-card>
|
||||
<div id="unanalyzed-product-wrapper" slot="content">
|
||||
<img id="unanalyzed-product-icon" role="presentation" alt=""></img>
|
||||
<div id="unanalyzed-product-message-content">
|
||||
<h2
|
||||
data-l10n-id="shopping-unanalyzed-product-header-2"
|
||||
></h2>
|
||||
<p data-l10n-id="shopping-unanalyzed-product-message-2"></p>
|
||||
</div>
|
||||
<button
|
||||
id="unanalyzed-product-analysis-button"
|
||||
class="primary"
|
||||
data-l10n-id="shopping-unanalyzed-product-analyze-button"
|
||||
@click=${this.onClickAnalysisButton}
|
||||
></button>
|
||||
</div>
|
||||
</shopping-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("unanalyzed-product-card", UnanalyzedProductCard);
|
||||
@@ -1,31 +0,0 @@
|
||||
# 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/.
|
||||
|
||||
browser.jar:
|
||||
content/browser/shopping/onboarding.mjs (content/onboarding.mjs)
|
||||
content/browser/shopping/shopping.html (content/shopping.html)
|
||||
content/browser/shopping/shopping-container.css (content/shopping-container.css)
|
||||
content/browser/shopping/shopping-page.css (content/shopping-page.css)
|
||||
content/browser/shopping/shopping-sidebar.js (content/shopping-sidebar.js)
|
||||
content/browser/shopping/shopping-message-bar.css (content/shopping-message-bar.css)
|
||||
content/browser/shopping/shopping-message-bar.mjs (content/shopping-message-bar.mjs)
|
||||
content/browser/shopping/highlights.mjs (content/highlights.mjs)
|
||||
content/browser/shopping/highlight-item.css (content/highlight-item.css)
|
||||
content/browser/shopping/highlight-item.mjs (content/highlight-item.mjs)
|
||||
content/browser/shopping/shopping-card.css (content/shopping-card.css)
|
||||
content/browser/shopping/shopping-card.mjs (content/shopping-card.mjs)
|
||||
content/browser/shopping/letter-grade.css (content/letter-grade.css)
|
||||
content/browser/shopping/letter-grade.mjs (content/letter-grade.mjs)
|
||||
content/browser/shopping/settings.mjs (content/settings.mjs)
|
||||
content/browser/shopping/settings.css (content/settings.css)
|
||||
content/browser/shopping/shopping-container.mjs (content/shopping-container.mjs)
|
||||
content/browser/shopping/adjusted-rating.mjs (content/adjusted-rating.mjs)
|
||||
content/browser/shopping/reliability.mjs (content/reliability.mjs)
|
||||
content/browser/shopping/analysis-explainer.css (content/analysis-explainer.css)
|
||||
content/browser/shopping/analysis-explainer.mjs (content/analysis-explainer.mjs)
|
||||
content/browser/shopping/unanalyzed.css (content/unanalyzed.css)
|
||||
content/browser/shopping/unanalyzed.mjs (content/unanalyzed.mjs)
|
||||
content/browser/shopping/recommended-ad.css (content/recommended-ad.css)
|
||||
content/browser/shopping/recommended-ad.mjs (content/recommended-ad.mjs)
|
||||
content/browser/shopping/assets/ (content/assets/*)
|
||||
@@ -1,19 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ["jar.mn"]
|
||||
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
"ShoppingSidebarChild.sys.mjs",
|
||||
"ShoppingSidebarParent.sys.mjs",
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"ShoppingUtils.sys.mjs",
|
||||
]
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Firefox", "Shopping")
|
||||