Bug 1932205 - Configure IAB Banner positions r=home-newtab-reviewers,reemhamz

Differential Revision: https://phabricator.services.mozilla.com/D231752
This commit is contained in:
Reem H
2024-12-13 15:44:04 +00:00
parent 0dfda0c4f4
commit b08ead089d
10 changed files with 523 additions and 251 deletions

View File

@@ -134,6 +134,7 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
this.refreshTopicSelectionCache.bind(this);
this.toggleTBRFeed = this.toggleTBRFeed.bind(this);
this.handleSectionsToggle = this.handleSectionsToggle.bind(this);
this.toggleIABBanners = this.toggleIABBanners.bind(this);
this.state = {
toggledStories: {},
weatherQuery: "",
@@ -228,6 +229,41 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
this.props.dispatch(ac.SetPref("weather.query", weatherQuery));
}
toggleIABBanners(e) {
const { pressed, id } = e.target;
const billboardEnabled = this.props.otherPrefs["newtabAdSize.billboard"];
const leaderboardEnabled =
this.props.otherPrefs["newtabAdSize.leaderboard"];
let spocValue;
let spocCount;
if (id === "billboard") {
this.props.dispatch(ac.SetPref("newtabAdSize.billboard", pressed));
if (pressed) {
spocValue = `newtab_spocs, newtab_billboard${leaderboardEnabled ? ", newtab_leaderboard" : ""}`;
spocCount = `6,1${leaderboardEnabled ? ",1" : ""}`;
} else {
spocValue = `newtab_spocs${leaderboardEnabled ? ", newtab_leaderboard" : ""}`;
spocCount = `6${leaderboardEnabled ? ",1" : ""}`;
}
} else if (id === "leaderboard") {
this.props.dispatch(ac.SetPref("newtabAdSize.leaderboard", pressed));
if (pressed) {
spocValue = `newtab_spocs, newtab_leaderboard${billboardEnabled ? ", newtab_billboard" : ""}`;
spocCount = `6,1${billboardEnabled ? ",1" : ""}`;
} else {
spocValue = `newtab_spocs${billboardEnabled ? ", newtab_billboard" : ""}`;
spocCount = `6${billboardEnabled ? ",1" : ""}`;
}
}
this.props.dispatch(
ac.SetPref("discoverystream.placements.spocs", spocValue)
);
this.props.dispatch(
ac.SetPref("discoverystream.placements.spocs.counts", spocCount)
);
}
handleSectionsToggle(e) {
const { pressed } = e.target;
this.props.dispatch(
@@ -485,6 +521,17 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
.split(",")
.map(s => s.trim())
.filter(item => item);
// Prefs for IAB Banners
const billboardsEnabled = this.props.otherPrefs["newtabAdSize.billboard"];
const leaderboardEnabled =
this.props.otherPrefs["newtabAdSize.leaderboard"];
const spocPlacements =
this.props.otherPrefs["discoverystream.placements.spocs"];
const billboardPressed =
billboardsEnabled && spocPlacements.includes("newtab_billboard");
const leaderboardPressed =
leaderboardEnabled && spocPlacements.includes("newtab_leaderboard");
return (
<div>
<button className="button" onClick={this.restorePrefDefaults}>
@@ -533,6 +580,26 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
label="Toggle DS Sections"
/>
</div>
{/* Collapsible Sections for experiments for easy on/off */}
<details className="details-section">
<summary>IAB Banner Ad Sizes</summary>
<div className="toggle-wrapper">
<moz-toggle
id="leaderboard"
pressed={leaderboardPressed || null}
onToggle={this.toggleIABBanners}
label="Enable IAB Leaderboard"
/>
</div>
<div className="toggle-wrapper">
<moz-toggle
id="billboard"
pressed={billboardPressed || null}
onToggle={this.toggleIABBanners}
label="Enable IAB Billboard"
/>
</div>
</details>
<table>
<tbody>
{prefToggles.map(pref => (

View File

@@ -124,6 +124,14 @@
width: 200px;
}
.details-section {
margin-block: var(--space-large);
summary {
font-size: var(--font-size-large);
}
}
table {
border-collapse: collapse;
width: 100%;

View File

@@ -8,7 +8,7 @@ import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
export const AdBanner = ({ spoc, dispatch, firstVisibleTimestamp }) => {
export const AdBanner = ({ spoc, dispatch, firstVisibleTimestamp, row }) => {
const getDimensions = format => {
switch (format) {
case "leaderboard":
@@ -57,46 +57,56 @@ export const AdBanner = ({ spoc, dispatch, firstVisibleTimestamp }) => {
);
};
// in the default card grid 1 would come before the 1st row of cards and 9 comes after the last row
// using clamp to make sure its between valid values (1-9)
const clampedRow = Math.max(1, Math.min(9, row));
return (
<aside className={`ad-banner-wrapper ${spoc.format}`}>
<div className="ad-banner-dismiss">
<button
className="icon icon-dismiss"
onClick={handleDismissClick}
data-l10n-id="newtab-toast-dismiss-button"
></button>
</div>
<SafeAnchor className="ad-banner-link" url={spoc.url} title={spoc.title}>
<ImpressionStats
flightId={spoc.flight_id}
rows={[
{
id: spoc.id,
pos: spoc.pos,
corpus_item_id: spoc.corpus_item_id,
scheduled_corpus_item_id: spoc.scheduled_corpus_item_id,
recommended_at: spoc.recommended_at,
received_rank: spoc.received_rank,
},
]}
dispatch={dispatch}
firstVisibleTimestamp={firstVisibleTimestamp}
/>
<div className="ad-banner-content">
<img
src={spoc.raw_image_src}
alt={spoc.alt_text}
loading="lazy"
width={imgWidth}
height={imgHeight}
<aside className={`ad-banner-wrapper`} style={{ gridRow: clampedRow }}>
<div className={`ad-banner-inner ${spoc.format}`}>
<div className="ad-banner-dismiss">
<button
className="icon icon-dismiss"
onClick={handleDismissClick}
data-l10n-id="newtab-toast-dismiss-button"
></button>
</div>
<SafeAnchor
className="ad-banner-link"
url={spoc.url}
title={spoc.title}
>
<ImpressionStats
flightId={spoc.flight_id}
rows={[
{
id: spoc.id,
pos: spoc.pos,
corpus_item_id: spoc.corpus_item_id,
scheduled_corpus_item_id: spoc.scheduled_corpus_item_id,
recommended_at: spoc.recommended_at,
received_rank: spoc.received_rank,
},
]}
dispatch={dispatch}
firstVisibleTimestamp={firstVisibleTimestamp}
/>
<div className="ad-banner-content">
<img
src={spoc.raw_image_src}
alt={spoc.alt_text}
loading="eager"
width={imgWidth}
height={imgHeight}
/>
</div>
</SafeAnchor>
<div className="ad-banner-sponsored">
<span
className="ad-banner-sponsored-label"
data-l10n-id="newtab-topsite-sponsored"
/>
</div>
</SafeAnchor>
<div className="ad-banner-sponsored">
<span
className="ad-banner-sponsored-label"
data-l10n-id="newtab-topsite-sponsored"
/>
</div>
</aside>
);

View File

@@ -1,63 +1,80 @@
.ad-banner-wrapper {
--billboard-width: 970px;
--billboard-height: 250px;
--leaderboard-width: 728px;
--leaderboard-height: 90px;
grid-column: 1/-1;
margin: 24px 0;
overflow: hidden;
// allow the ad banner to take up full width
// of screen rather than card-grid width
width: 100vw;
margin-inline-start: 50%;
transform: translate3d(-50%, 0, 0);
.ad-banner-dismiss {
margin: 10px auto;
text-align: end;
margin-inline-end: 10px;
.ad-banner-inner {
margin-inline: auto;
.icon-dismiss {
background-size: 20px;
border: 0;
cursor: pointer;
}
}
.ad-banner-sponsored {
margin: 13px auto;
span {
text-transform: uppercase;
font-size: var(--font-size-small);
color: var(--newtab-contextual-text-secondary-color);
}
}
&.leaderboard {
.ad-banner-dismiss {
width: 728px;
margin-block: 0 var(--space-small);
margin-inline: 0 var(--space-xxsmall);
text-align: end;
.icon-dismiss {
background-size: var(--size-item-small);
border: 0;
cursor: pointer;
}
}
.ad-banner-content {
height: 90px;
width: 728px;
margin: 0 auto;
&.leaderboard {
max-width: var(--leaderboard-width);
.ad-banner-dismiss {
width: var(--leaderboard-width);
}
.ad-banner-content {
height: var(--leaderboard-height);
width: var(--leaderboard-width);
margin: 0 auto;
}
.ad-banner-sponsored {
width: var(--leaderboard-width);
}
@media (width <= 758px) {
display: none;
}
}
&.billboard {
max-width: var(--billboard-width);
.ad-banner-content {
height: var(--billboard-height);
width: var(--billboard-width);
margin: 0 auto;
}
.ad-banner-sponsored {
width: var(--billboard-width);
}
@media (width <= 1015px) {
display: none;
}
}
.ad-banner-sponsored {
width: 728px;
}
margin-block: var(--space-small) 0;
@media (max-width: $break-point-large) {
display: none;
}
}
&.billboard {
.ad-banner-content {
height: 250px;
width: 970px;
margin: 0 auto;
}
.ad-banner-sponsored {
width: 970px;
}
@media (max-width: $break-point-widest) {
display: none;
span {
text-transform: uppercase;
font-size: var(--font-size-small);
color: var(--newtab-contextual-text-secondary-color);
}
}
}
}

View File

@@ -27,6 +27,10 @@ const PREF_LIST_FEED_SELECTED_FEED =
"discoverystream.contextualContent.selectedFeed";
const PREF_FAKESPOT_ENABLED =
"discoverystream.contextualContent.fakespot.enabled";
const PREF_BILLBOARD_ENABLED = "newtabAdSize.billboard";
const PREF_LEADERBOARD_ENABLED = "newtabAdSize.leaderboard";
const PREF_LEADERBOARD_POSITION = "newtabAdSize.billboard.position";
const PREF_BILLBOARD_POSITION = "newtabAdSize.billboard.position";
const INTERSECTION_RATIO = 0.5;
const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";
@@ -354,6 +358,8 @@ export class _CardGrid extends React.PureComponent {
const spocsStartupCacheEnabled = prefs[PREF_SPOCS_STARTUPCACHE_ENABLED];
const listFeedEnabled = prefs[PREF_LIST_FEED_ENABLED];
const listFeedSelectedFeed = prefs[PREF_LIST_FEED_SELECTED_FEED];
const billboardEnabled = prefs[PREF_BILLBOARD_ENABLED];
const leaderboardEnabled = prefs[PREF_LEADERBOARD_ENABLED];
// filter out recs that should be in ListFeed
const recs = this.props.data.recommendations
.filter(item => !item.feedName)
@@ -365,74 +371,61 @@ export class _CardGrid extends React.PureComponent {
for (let index = 0; index < items; index++) {
const rec = recs[index];
if (rec?.format === "billboard" || rec?.format === "leaderboard") {
cards.push(
<AdBanner
spoc={rec}
cards.push(
topicsLoading ||
!rec ||
rec.placeholder ||
(rec.flight_id &&
!spocsStartupCacheEnabled &&
this.props.App.isForStartupCache) ? (
<PlaceholderDSCard key={`dscard-${index}`} />
) : (
<DSCard
key={`dscard-${rec.id}`}
dispatch={this.props.dispatch}
pos={rec.pos}
flightId={rec.flight_id}
image_src={rec.image_src}
raw_image_src={rec.raw_image_src}
word_count={rec.word_count}
time_to_read={rec.time_to_read}
title={rec.title}
topic={rec.topic}
showTopics={showTopics}
selectedTopics={selectedTopics}
availableTopics={availableTopics}
excerpt={rec.excerpt}
url={rec.url}
id={rec.id}
shim={rec.shim}
fetchTimestamp={rec.fetchTimestamp}
type={this.props.type}
context={rec.context}
sponsor={rec.sponsor}
sponsored_by_override={rec.sponsored_by_override}
dispatch={this.props.dispatch}
source={rec.domain}
publisher={rec.publisher}
pocket_id={rec.pocket_id}
context_type={rec.context_type}
bookmarkGuid={rec.bookmarkGuid}
is_collection={this.props.is_collection}
saveToPocketCard={saveToPocketCard}
ctaButtonSponsors={ctaButtonSponsors}
ctaButtonVariant={ctaButtonVariant}
spocMessageVariant={spocMessageVariant}
recommendation_id={rec.recommendation_id}
firstVisibleTimestamp={this.props.firstVisibleTimestamp}
mayHaveThumbsUpDown={mayHaveThumbsUpDown}
mayHaveSectionsCards={mayHaveSectionsCards}
corpus_item_id={rec.corpus_item_id}
scheduled_corpus_item_id={rec.scheduled_corpus_item_id}
recommended_at={rec.recommended_at}
received_rank={rec.received_rank}
format={rec.format}
alt_text={rec.alt_text}
/>
);
} else {
cards.push(
topicsLoading ||
!rec ||
rec.placeholder ||
(rec.flight_id &&
!spocsStartupCacheEnabled &&
this.props.App.isForStartupCache) ? (
<PlaceholderDSCard key={`dscard-${index}`} />
) : (
<DSCard
key={`dscard-${rec.id}`}
pos={rec.pos}
flightId={rec.flight_id}
image_src={rec.image_src}
raw_image_src={rec.raw_image_src}
word_count={rec.word_count}
time_to_read={rec.time_to_read}
title={rec.title}
topic={rec.topic}
showTopics={showTopics}
selectedTopics={selectedTopics}
availableTopics={availableTopics}
excerpt={rec.excerpt}
url={rec.url}
id={rec.id}
shim={rec.shim}
fetchTimestamp={rec.fetchTimestamp}
type={this.props.type}
context={rec.context}
sponsor={rec.sponsor}
sponsored_by_override={rec.sponsored_by_override}
dispatch={this.props.dispatch}
source={rec.domain}
publisher={rec.publisher}
pocket_id={rec.pocket_id}
context_type={rec.context_type}
bookmarkGuid={rec.bookmarkGuid}
is_collection={this.props.is_collection}
saveToPocketCard={saveToPocketCard}
ctaButtonSponsors={ctaButtonSponsors}
ctaButtonVariant={ctaButtonVariant}
spocMessageVariant={spocMessageVariant}
recommendation_id={rec.recommendation_id}
firstVisibleTimestamp={this.props.firstVisibleTimestamp}
mayHaveThumbsUpDown={mayHaveThumbsUpDown}
mayHaveSectionsCards={mayHaveSectionsCards}
corpus_item_id={rec.corpus_item_id}
scheduled_corpus_item_id={rec.scheduled_corpus_item_id}
recommended_at={rec.recommended_at}
received_rank={rec.received_rank}
format={rec.format}
alt_text={rec.alt_text}
/>
)
);
}
)
);
}
if (widgets?.positions?.length && widgets?.data?.length) {
@@ -485,6 +478,38 @@ export class _CardGrid extends React.PureComponent {
}
}
// if a banner ad is enabled and we have any available, place them in the grid
const { spocs } = this.props.DiscoveryStream;
if ((billboardEnabled || leaderboardEnabled) && spocs.data.newtab_spocs) {
const spocTypes = [
billboardEnabled && "billboard",
leaderboardEnabled && "leaderboard",
].filter(Boolean);
// We need to go through the billboards in `newtab_spocs` because they have been normalized
// in DiscoveryStreamFeed on line 1024
const bannerSpocs = spocs.data.newtab_spocs.items.filter(({ format }) =>
spocTypes.includes(format)
);
if (bannerSpocs.length) {
for (const spoc of bannerSpocs) {
const row =
spoc.format === "leaderboard"
? prefs[PREF_LEADERBOARD_POSITION]
: prefs[PREF_BILLBOARD_POSITION];
cards.push(
<AdBanner
spoc={spoc}
key={`dscard-${spoc.id}`}
dispatch={this.props.dispatch}
type={this.props.type}
firstVisibleTimestamp={this.props.firstVisibleTimestamp}
row={row}
/>
);
}
}
}
let moreRecsHeader = "";
// For now this is English only.
if (showRecentSaves || (essentialReadsHeader && editorsPicksHeader)) {

View File

@@ -27,8 +27,11 @@ export const selectLayoutRender = ({ state = {}, prefs = {} }) => {
const results = [...data];
for (let position of spocsPositions) {
const spoc = spocsData[spocIndexPlacementMap[placementName]];
const format = spoc?.format;
// If there are no spocs left, we can stop filling positions.
if (!spoc) {
// Since banner-type ads are placed by row and don't use the normal spoc-position,
// dont combine with content
if (!spoc || format === "billboard" || format === "leaderboard") {
break;
}

View File

@@ -3618,6 +3618,12 @@ main section {
margin-block: var(--space-large);
width: 200px;
}
.discoverystream-admin .details-section {
margin-block: var(--space-large);
}
.discoverystream-admin .details-section summary {
font-size: var(--font-size-large);
}
.discoverystream-admin table {
border-collapse: collapse;
width: 100%;
@@ -7622,54 +7628,69 @@ main section {
}
.ad-banner-wrapper {
--billboard-width: 970px;
--billboard-height: 250px;
--leaderboard-width: 728px;
--leaderboard-height: 90px;
grid-column: 1/-1;
margin: 24px 0;
overflow: hidden;
width: 100vw;
margin-inline-start: 50%;
transform: translate3d(-50%, 0, 0);
}
.ad-banner-wrapper .ad-banner-dismiss {
margin: 10px auto;
.ad-banner-wrapper .ad-banner-inner {
margin-inline: auto;
}
.ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss {
margin-block: 0 var(--space-small);
margin-inline: 0 var(--space-xxsmall);
text-align: end;
margin-inline-end: 10px;
}
.ad-banner-wrapper .ad-banner-dismiss .icon-dismiss {
background-size: 20px;
.ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss .icon-dismiss {
background-size: var(--size-item-small);
border: 0;
cursor: pointer;
}
.ad-banner-wrapper .ad-banner-sponsored {
margin: 13px auto;
.ad-banner-wrapper .ad-banner-inner.leaderboard {
max-width: var(--leaderboard-width);
}
.ad-banner-wrapper .ad-banner-sponsored span {
.ad-banner-wrapper .ad-banner-inner.leaderboard .ad-banner-dismiss {
width: var(--leaderboard-width);
}
.ad-banner-wrapper .ad-banner-inner.leaderboard .ad-banner-content {
height: var(--leaderboard-height);
width: var(--leaderboard-width);
margin: 0 auto;
}
.ad-banner-wrapper .ad-banner-inner.leaderboard .ad-banner-sponsored {
width: var(--leaderboard-width);
}
@media (width <= 758px) {
.ad-banner-wrapper .ad-banner-inner.leaderboard {
display: none;
}
}
.ad-banner-wrapper .ad-banner-inner.billboard {
max-width: var(--billboard-width);
}
.ad-banner-wrapper .ad-banner-inner.billboard .ad-banner-content {
height: var(--billboard-height);
width: var(--billboard-width);
margin: 0 auto;
}
.ad-banner-wrapper .ad-banner-inner.billboard .ad-banner-sponsored {
width: var(--billboard-width);
}
@media (width <= 1015px) {
.ad-banner-wrapper .ad-banner-inner.billboard {
display: none;
}
}
.ad-banner-wrapper .ad-banner-inner .ad-banner-sponsored {
margin-block: var(--space-small) 0;
}
.ad-banner-wrapper .ad-banner-inner .ad-banner-sponsored span {
text-transform: uppercase;
font-size: var(--font-size-small);
color: var(--newtab-contextual-text-secondary-color);
}
.ad-banner-wrapper.leaderboard .ad-banner-dismiss {
width: 728px;
}
.ad-banner-wrapper.leaderboard .ad-banner-content {
height: 90px;
width: 728px;
margin: 0 auto;
}
.ad-banner-wrapper.leaderboard .ad-banner-sponsored {
width: 728px;
}
@media (max-width: 866px) {
.ad-banner-wrapper.leaderboard {
display: none;
}
}
.ad-banner-wrapper.billboard .ad-banner-content {
height: 250px;
width: 970px;
margin: 0 auto;
}
.ad-banner-wrapper.billboard .ad-banner-sponsored {
width: 970px;
}
@media (max-width: 1122px) {
.ad-banner-wrapper.billboard {
display: none;
}
}

View File

@@ -700,6 +700,7 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
this.refreshTopicSelectionCache = this.refreshTopicSelectionCache.bind(this);
this.toggleTBRFeed = this.toggleTBRFeed.bind(this);
this.handleSectionsToggle = this.handleSectionsToggle.bind(this);
this.toggleIABBanners = this.toggleIABBanners.bind(this);
this.state = {
toggledStories: {},
weatherQuery: ""
@@ -774,6 +775,37 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
} = this.state;
this.props.dispatch(actionCreators.SetPref("weather.query", weatherQuery));
}
toggleIABBanners(e) {
const {
pressed,
id
} = e.target;
const billboardEnabled = this.props.otherPrefs["newtabAdSize.billboard"];
const leaderboardEnabled = this.props.otherPrefs["newtabAdSize.leaderboard"];
let spocValue;
let spocCount;
if (id === "billboard") {
this.props.dispatch(actionCreators.SetPref("newtabAdSize.billboard", pressed));
if (pressed) {
spocValue = `newtab_spocs, newtab_billboard${leaderboardEnabled ? ", newtab_leaderboard" : ""}`;
spocCount = `6,1${leaderboardEnabled ? ",1" : ""}`;
} else {
spocValue = `newtab_spocs${leaderboardEnabled ? ", newtab_leaderboard" : ""}`;
spocCount = `6${leaderboardEnabled ? ",1" : ""}`;
}
} else if (id === "leaderboard") {
this.props.dispatch(actionCreators.SetPref("newtabAdSize.leaderboard", pressed));
if (pressed) {
spocValue = `newtab_spocs, newtab_leaderboard${billboardEnabled ? ", newtab_billboard" : ""}`;
spocCount = `6,1${billboardEnabled ? ",1" : ""}`;
} else {
spocValue = `newtab_spocs${billboardEnabled ? ", newtab_billboard" : ""}`;
spocCount = `6${billboardEnabled ? ",1" : ""}`;
}
}
this.props.dispatch(actionCreators.SetPref("discoverystream.placements.spocs", spocValue));
this.props.dispatch(actionCreators.SetPref("discoverystream.placements.spocs.counts", spocCount));
}
handleSectionsToggle(e) {
const {
pressed
@@ -928,6 +960,13 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
const selectedFeed = this.props.otherPrefs["discoverystream.contextualContent.selectedFeed"];
const sectionsEnabled = this.props.otherPrefs["discoverystream.sections.enabled"];
const TBRFeeds = this.props.otherPrefs["discoverystream.contextualContent.feeds"].split(",").map(s => s.trim()).filter(item => item);
// Prefs for IAB Banners
const billboardsEnabled = this.props.otherPrefs["newtabAdSize.billboard"];
const leaderboardEnabled = this.props.otherPrefs["newtabAdSize.leaderboard"];
const spocPlacements = this.props.otherPrefs["discoverystream.placements.spocs"];
const billboardPressed = billboardsEnabled && spocPlacements.includes("newtab_billboard");
const leaderboardPressed = leaderboardEnabled && spocPlacements.includes("newtab_leaderboard");
return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", {
className: "button",
onClick: this.restorePrefDefaults
@@ -966,7 +1005,23 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
pressed: sectionsEnabled || null,
onToggle: this.handleSectionsToggle,
label: "Toggle DS Sections"
})), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, prefToggles.map(pref => /*#__PURE__*/external_React_default().createElement(Row, {
})), /*#__PURE__*/external_React_default().createElement("details", {
className: "details-section"
}, /*#__PURE__*/external_React_default().createElement("summary", null, "IAB Banner Ad Sizes"), /*#__PURE__*/external_React_default().createElement("div", {
className: "toggle-wrapper"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
id: "leaderboard",
pressed: leaderboardPressed || null,
onToggle: this.toggleIABBanners,
label: "Enable IAB Leaderboard"
})), /*#__PURE__*/external_React_default().createElement("div", {
className: "toggle-wrapper"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
id: "billboard",
pressed: billboardPressed || null,
onToggle: this.toggleIABBanners,
label: "Enable IAB Billboard"
}))), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, prefToggles.map(pref => /*#__PURE__*/external_React_default().createElement(Row, {
key: pref
}, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, {
checked: config[pref],
@@ -4110,7 +4165,8 @@ function ListFeed({
const AdBanner = ({
spoc,
dispatch,
firstVisibleTimestamp
firstVisibleTimestamp,
row
}) => {
const getDimensions = format => {
switch (format) {
@@ -4156,8 +4212,17 @@ const AdBanner = ({
}]
}));
};
// in the default card grid 1 would come before the 1st row of cards and 9 comes after the last row
// using clamp to make sure its between valid values (1-9)
const clampedRow = Math.max(1, Math.min(9, row));
return /*#__PURE__*/external_React_default().createElement("aside", {
className: `ad-banner-wrapper ${spoc.format}`
className: `ad-banner-wrapper`,
style: {
gridRow: clampedRow
}
}, /*#__PURE__*/external_React_default().createElement("div", {
className: `ad-banner-inner ${spoc.format}`
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "ad-banner-dismiss"
}, /*#__PURE__*/external_React_default().createElement("button", {
@@ -4185,7 +4250,7 @@ const AdBanner = ({
}, /*#__PURE__*/external_React_default().createElement("img", {
src: spoc.raw_image_src,
alt: spoc.alt_text,
loading: "lazy",
loading: "eager",
width: imgWidth,
height: imgHeight
}))), /*#__PURE__*/external_React_default().createElement("div", {
@@ -4193,7 +4258,7 @@ const AdBanner = ({
}, /*#__PURE__*/external_React_default().createElement("span", {
className: "ad-banner-sponsored-label",
"data-l10n-id": "newtab-topsite-sponsored"
})));
}))));
};
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
@@ -4221,6 +4286,10 @@ const PREF_SPOCS_STARTUPCACHE_ENABLED = "discoverystream.spocs.startupCache.enab
const PREF_LIST_FEED_ENABLED = "discoverystream.contextualContent.enabled";
const PREF_LIST_FEED_SELECTED_FEED = "discoverystream.contextualContent.selectedFeed";
const PREF_FAKESPOT_ENABLED = "discoverystream.contextualContent.fakespot.enabled";
const PREF_BILLBOARD_ENABLED = "newtabAdSize.billboard";
const PREF_LEADERBOARD_ENABLED = "newtabAdSize.leaderboard";
const PREF_LEADERBOARD_POSITION = "newtabAdSize.billboard.position";
const PREF_BILLBOARD_POSITION = "newtabAdSize.billboard.position";
const CardGrid_INTERSECTION_RATIO = 0.5;
const CardGrid_VISIBLE = "visible";
const CardGrid_VISIBILITY_CHANGE_EVENT = "visibilitychange";
@@ -4501,6 +4570,8 @@ class _CardGrid extends (external_React_default()).PureComponent {
const spocsStartupCacheEnabled = prefs[PREF_SPOCS_STARTUPCACHE_ENABLED];
const listFeedEnabled = prefs[PREF_LIST_FEED_ENABLED];
const listFeedSelectedFeed = prefs[PREF_LIST_FEED_SELECTED_FEED];
const billboardEnabled = prefs[PREF_BILLBOARD_ENABLED];
const leaderboardEnabled = prefs[PREF_LEADERBOARD_ENABLED];
// filter out recs that should be in ListFeed
const recs = this.props.data.recommendations.filter(item => !item.feedName).slice(0, items);
const cards = [];
@@ -4508,62 +4579,52 @@ class _CardGrid extends (external_React_default()).PureComponent {
let editorsPicksCards = [];
for (let index = 0; index < items; index++) {
const rec = recs[index];
if (rec?.format === "billboard" || rec?.format === "leaderboard") {
cards.push( /*#__PURE__*/external_React_default().createElement(AdBanner, {
spoc: rec,
key: `dscard-${rec.id}`,
dispatch: this.props.dispatch,
type: this.props.type,
firstVisibleTimestamp: this.props.firstVisibleTimestamp
}));
} else {
cards.push(topicsLoading || !rec || rec.placeholder || rec.flight_id && !spocsStartupCacheEnabled && this.props.App.isForStartupCache ? /*#__PURE__*/external_React_default().createElement(PlaceholderDSCard, {
key: `dscard-${index}`
}) : /*#__PURE__*/external_React_default().createElement(DSCard, {
key: `dscard-${rec.id}`,
pos: rec.pos,
flightId: rec.flight_id,
image_src: rec.image_src,
raw_image_src: rec.raw_image_src,
word_count: rec.word_count,
time_to_read: rec.time_to_read,
title: rec.title,
topic: rec.topic,
showTopics: showTopics,
selectedTopics: selectedTopics,
availableTopics: availableTopics,
excerpt: rec.excerpt,
url: rec.url,
id: rec.id,
shim: rec.shim,
fetchTimestamp: rec.fetchTimestamp,
type: this.props.type,
context: rec.context,
sponsor: rec.sponsor,
sponsored_by_override: rec.sponsored_by_override,
dispatch: this.props.dispatch,
source: rec.domain,
publisher: rec.publisher,
pocket_id: rec.pocket_id,
context_type: rec.context_type,
bookmarkGuid: rec.bookmarkGuid,
is_collection: this.props.is_collection,
saveToPocketCard: saveToPocketCard,
ctaButtonSponsors: ctaButtonSponsors,
ctaButtonVariant: ctaButtonVariant,
spocMessageVariant: spocMessageVariant,
recommendation_id: rec.recommendation_id,
firstVisibleTimestamp: this.props.firstVisibleTimestamp,
mayHaveThumbsUpDown: mayHaveThumbsUpDown,
mayHaveSectionsCards: mayHaveSectionsCards,
corpus_item_id: rec.corpus_item_id,
scheduled_corpus_item_id: rec.scheduled_corpus_item_id,
recommended_at: rec.recommended_at,
received_rank: rec.received_rank,
format: rec.format,
alt_text: rec.alt_text
}));
}
cards.push(topicsLoading || !rec || rec.placeholder || rec.flight_id && !spocsStartupCacheEnabled && this.props.App.isForStartupCache ? /*#__PURE__*/external_React_default().createElement(PlaceholderDSCard, {
key: `dscard-${index}`
}) : /*#__PURE__*/external_React_default().createElement(DSCard, {
key: `dscard-${rec.id}`,
pos: rec.pos,
flightId: rec.flight_id,
image_src: rec.image_src,
raw_image_src: rec.raw_image_src,
word_count: rec.word_count,
time_to_read: rec.time_to_read,
title: rec.title,
topic: rec.topic,
showTopics: showTopics,
selectedTopics: selectedTopics,
availableTopics: availableTopics,
excerpt: rec.excerpt,
url: rec.url,
id: rec.id,
shim: rec.shim,
fetchTimestamp: rec.fetchTimestamp,
type: this.props.type,
context: rec.context,
sponsor: rec.sponsor,
sponsored_by_override: rec.sponsored_by_override,
dispatch: this.props.dispatch,
source: rec.domain,
publisher: rec.publisher,
pocket_id: rec.pocket_id,
context_type: rec.context_type,
bookmarkGuid: rec.bookmarkGuid,
is_collection: this.props.is_collection,
saveToPocketCard: saveToPocketCard,
ctaButtonSponsors: ctaButtonSponsors,
ctaButtonVariant: ctaButtonVariant,
spocMessageVariant: spocMessageVariant,
recommendation_id: rec.recommendation_id,
firstVisibleTimestamp: this.props.firstVisibleTimestamp,
mayHaveThumbsUpDown: mayHaveThumbsUpDown,
mayHaveSectionsCards: mayHaveSectionsCards,
corpus_item_id: rec.corpus_item_id,
scheduled_corpus_item_id: rec.scheduled_corpus_item_id,
recommended_at: rec.recommended_at,
received_rank: rec.received_rank,
format: rec.format,
alt_text: rec.alt_text
}));
}
if (widgets?.positions?.length && widgets?.data?.length) {
let positionIndex = 0;
@@ -4602,6 +4663,32 @@ class _CardGrid extends (external_React_default()).PureComponent {
cards.splice(2, 1, this.renderListFeed(this.props.data.recommendations, listFeedSelectedFeed));
}
}
// if a banner ad is enabled and we have any available, place them in the grid
const {
spocs
} = this.props.DiscoveryStream;
if ((billboardEnabled || leaderboardEnabled) && spocs.data.newtab_spocs) {
const spocTypes = [billboardEnabled && "billboard", leaderboardEnabled && "leaderboard"].filter(Boolean);
// We need to go through the billboards in `newtab_spocs` because they have been normalized
// in DiscoveryStreamFeed on line 1024
const bannerSpocs = spocs.data.newtab_spocs.items.filter(({
format
}) => spocTypes.includes(format));
if (bannerSpocs.length) {
for (const spoc of bannerSpocs) {
const row = spoc.format === "leaderboard" ? prefs[PREF_LEADERBOARD_POSITION] : prefs[PREF_BILLBOARD_POSITION];
cards.push( /*#__PURE__*/external_React_default().createElement(AdBanner, {
spoc: spoc,
key: `dscard-${spoc.id}`,
dispatch: this.props.dispatch,
type: this.props.type,
firstVisibleTimestamp: this.props.firstVisibleTimestamp,
row: row
}));
}
}
}
let moreRecsHeader = "";
// For now this is English only.
if (showRecentSaves || essentialReadsHeader && editorsPicksHeader) {
@@ -9372,8 +9459,11 @@ const selectLayoutRender = ({ state = {}, prefs = {} }) => {
const results = [...data];
for (let position of spocsPositions) {
const spoc = spocsData[spocIndexPlacementMap[placementName]];
const format = spoc?.format;
// If there are no spocs left, we can stop filling positions.
if (!spoc) {
// Since banner-type ads are placed by row and don't use the normal spoc-position,
// dont combine with content
if (!spoc || format === "billboard" || format === "leaderboard") {
break;
}

View File

@@ -436,6 +436,14 @@ export const PREFS_CONFIG = new Map([
value: false,
},
],
[
"newtabAdSize.leaderboard.position",
{
title:
"CSV string of positions for leaderboard spocs - should corralate to a row in DS grid",
value: "1",
},
],
[
"newtabAdSize.billboard",
{
@@ -443,6 +451,14 @@ export const PREFS_CONFIG = new Map([
value: false,
},
],
[
"newtabAdSize.billboard.position",
{
title:
"number string of positions for billboard spocs - should corralate to a row in DS grid",
value: "1",
},
],
[
"newtabLayouts.variant-a",
{

View File

@@ -858,6 +858,13 @@ newtabAdSizingExperiment:
pref: browser.newtabpage.activity-stream.newtabAdSize.leaderboard
description: >-
Leaderboard ad size and UI.
leaderboard_position:
type: string
setPref:
branch: user
pref: browser.newtabpage.activity-stream.newtabAdSize.leaderboard.position
description: >-
Leaderboard row position.
billboard:
type: boolean
setPref:
@@ -865,6 +872,14 @@ newtabAdSizingExperiment:
pref: browser.newtabpage.activity-stream.newtabAdSize.billboard
description: >-
Billboard ad size and UI.
billboard_position:
type: string
setPref:
branch: user
pref: browser.newtabpage.activity-stream.newtabAdSize.billboard.position
description: >-
Billboard row position.
newtabLayoutExperiment:
description: >-
Change the default layout of new tab by adjusting sizes and spacing of elements.