/* 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 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";
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 },
isOverflow: { type: Boolean },
autoOpenEnabled: { type: Boolean },
autoOpenEnabledByUser: { type: Boolean },
showingKeepClosedMessage: { type: Boolean },
};
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",
};
}
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,
})
);
}
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.productUrl = productUrl;
this.recommendationData = recommendationData;
this.isOffline = !navigator.onLine;
this.isAnalysisInProgress = isAnalysisInProgress;
this.adsEnabled = adsEnabled;
this.adsEnabledByUser = adsEnabledByUser;
this.analysisProgress = analysisProgress;
this.focusCloseButton = focusCloseButton;
this.autoOpenEnabled = autoOpenEnabled;
this.autoOpenEnabledByUser = 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":
let scrollYPosition = window.scrollY;
this.isOverflow = scrollYPosition > 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;
}
}
getHostnameFromProductUrl() {
let hostname;
try {
hostname = new URL(this.productUrl)?.hostname;
return hostname;
} catch (e) {
console.error(`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 lang;
let hostname = this.getHostnameFromProductUrl();
switch (hostname) {
case "www.amazon.fr":
lang = "fr";
break;
case "www.amazon.de":
lang = "de";
break;
default:
lang = "en";
}
return html`