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.refreshTopicSelectionCache.bind(this);
this.toggleTBRFeed = this.toggleTBRFeed.bind(this); this.toggleTBRFeed = this.toggleTBRFeed.bind(this);
this.handleSectionsToggle = this.handleSectionsToggle.bind(this); this.handleSectionsToggle = this.handleSectionsToggle.bind(this);
this.toggleIABBanners = this.toggleIABBanners.bind(this);
this.state = { this.state = {
toggledStories: {}, toggledStories: {},
weatherQuery: "", weatherQuery: "",
@@ -228,6 +229,41 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
this.props.dispatch(ac.SetPref("weather.query", weatherQuery)); 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) { handleSectionsToggle(e) {
const { pressed } = e.target; const { pressed } = e.target;
this.props.dispatch( this.props.dispatch(
@@ -485,6 +521,17 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
.split(",") .split(",")
.map(s => s.trim()) .map(s => s.trim())
.filter(item => item); .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 ( return (
<div> <div>
<button className="button" onClick={this.restorePrefDefaults}> <button className="button" onClick={this.restorePrefDefaults}>
@@ -533,6 +580,26 @@ export class DiscoveryStreamAdminUI extends React.PureComponent {
label="Toggle DS Sections" label="Toggle DS Sections"
/> />
</div> </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> <table>
<tbody> <tbody>
{prefToggles.map(pref => ( {prefToggles.map(pref => (

View File

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

View File

@@ -8,7 +8,7 @@ import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats"; import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; 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 => { const getDimensions = format => {
switch (format) { switch (format) {
case "leaderboard": 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 ( return (
<aside className={`ad-banner-wrapper ${spoc.format}`}> <aside className={`ad-banner-wrapper`} style={{ gridRow: clampedRow }}>
<div className="ad-banner-dismiss"> <div className={`ad-banner-inner ${spoc.format}`}>
<button <div className="ad-banner-dismiss">
className="icon icon-dismiss" <button
onClick={handleDismissClick} className="icon icon-dismiss"
data-l10n-id="newtab-toast-dismiss-button" onClick={handleDismissClick}
></button> data-l10n-id="newtab-toast-dismiss-button"
</div> ></button>
<SafeAnchor className="ad-banner-link" url={spoc.url} title={spoc.title}> </div>
<ImpressionStats <SafeAnchor
flightId={spoc.flight_id} className="ad-banner-link"
rows={[ url={spoc.url}
{ title={spoc.title}
id: spoc.id, >
pos: spoc.pos, <ImpressionStats
corpus_item_id: spoc.corpus_item_id, flightId={spoc.flight_id}
scheduled_corpus_item_id: spoc.scheduled_corpus_item_id, rows={[
recommended_at: spoc.recommended_at, {
received_rank: spoc.received_rank, id: spoc.id,
}, pos: spoc.pos,
]} corpus_item_id: spoc.corpus_item_id,
dispatch={dispatch} scheduled_corpus_item_id: spoc.scheduled_corpus_item_id,
firstVisibleTimestamp={firstVisibleTimestamp} recommended_at: spoc.recommended_at,
/> received_rank: spoc.received_rank,
<div className="ad-banner-content"> },
<img ]}
src={spoc.raw_image_src} dispatch={dispatch}
alt={spoc.alt_text} firstVisibleTimestamp={firstVisibleTimestamp}
loading="lazy" />
width={imgWidth} <div className="ad-banner-content">
height={imgHeight} <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> </div>
</SafeAnchor>
<div className="ad-banner-sponsored">
<span
className="ad-banner-sponsored-label"
data-l10n-id="newtab-topsite-sponsored"
/>
</div> </div>
</aside> </aside>
); );

View File

@@ -1,63 +1,80 @@
.ad-banner-wrapper { .ad-banner-wrapper {
--billboard-width: 970px;
--billboard-height: 250px;
--leaderboard-width: 728px;
--leaderboard-height: 90px;
grid-column: 1/-1; grid-column: 1/-1;
margin: 24px 0;
overflow: hidden; 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 { .ad-banner-inner {
margin: 10px auto; margin-inline: auto;
text-align: end;
margin-inline-end: 10px;
.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 { .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 { &.leaderboard {
height: 90px; max-width: var(--leaderboard-width);
width: 728px;
margin: 0 auto; .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 { .ad-banner-sponsored {
width: 728px; margin-block: var(--space-small) 0;
}
@media (max-width: $break-point-large) { span {
display: none; text-transform: uppercase;
} font-size: var(--font-size-small);
} color: var(--newtab-contextual-text-secondary-color);
}
&.billboard {
.ad-banner-content {
height: 250px;
width: 970px;
margin: 0 auto;
}
.ad-banner-sponsored {
width: 970px;
}
@media (max-width: $break-point-widest) {
display: none;
} }
} }
} }

View File

@@ -27,6 +27,10 @@ const PREF_LIST_FEED_SELECTED_FEED =
"discoverystream.contextualContent.selectedFeed"; "discoverystream.contextualContent.selectedFeed";
const PREF_FAKESPOT_ENABLED = const PREF_FAKESPOT_ENABLED =
"discoverystream.contextualContent.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 INTERSECTION_RATIO = 0.5;
const VISIBLE = "visible"; const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange"; const VISIBILITY_CHANGE_EVENT = "visibilitychange";
@@ -354,6 +358,8 @@ export class _CardGrid extends React.PureComponent {
const spocsStartupCacheEnabled = prefs[PREF_SPOCS_STARTUPCACHE_ENABLED]; const spocsStartupCacheEnabled = prefs[PREF_SPOCS_STARTUPCACHE_ENABLED];
const listFeedEnabled = prefs[PREF_LIST_FEED_ENABLED]; const listFeedEnabled = prefs[PREF_LIST_FEED_ENABLED];
const listFeedSelectedFeed = prefs[PREF_LIST_FEED_SELECTED_FEED]; 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 // filter out recs that should be in ListFeed
const recs = this.props.data.recommendations const recs = this.props.data.recommendations
.filter(item => !item.feedName) .filter(item => !item.feedName)
@@ -365,74 +371,61 @@ export class _CardGrid extends React.PureComponent {
for (let index = 0; index < items; index++) { for (let index = 0; index < items; index++) {
const rec = recs[index]; const rec = recs[index];
cards.push(
if (rec?.format === "billboard" || rec?.format === "leaderboard") { topicsLoading ||
cards.push( !rec ||
<AdBanner rec.placeholder ||
spoc={rec} (rec.flight_id &&
!spocsStartupCacheEnabled &&
this.props.App.isForStartupCache) ? (
<PlaceholderDSCard key={`dscard-${index}`} />
) : (
<DSCard
key={`dscard-${rec.id}`} 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} 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} 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) { 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 = ""; let moreRecsHeader = "";
// For now this is English only. // For now this is English only.
if (showRecentSaves || (essentialReadsHeader && editorsPicksHeader)) { if (showRecentSaves || (essentialReadsHeader && editorsPicksHeader)) {

View File

@@ -27,8 +27,11 @@ export const selectLayoutRender = ({ state = {}, prefs = {} }) => {
const results = [...data]; const results = [...data];
for (let position of spocsPositions) { for (let position of spocsPositions) {
const spoc = spocsData[spocIndexPlacementMap[placementName]]; const spoc = spocsData[spocIndexPlacementMap[placementName]];
const format = spoc?.format;
// If there are no spocs left, we can stop filling positions. // 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; break;
} }

View File

@@ -3618,6 +3618,12 @@ main section {
margin-block: var(--space-large); margin-block: var(--space-large);
width: 200px; 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 { .discoverystream-admin table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
@@ -7622,54 +7628,69 @@ main section {
} }
.ad-banner-wrapper { .ad-banner-wrapper {
--billboard-width: 970px;
--billboard-height: 250px;
--leaderboard-width: 728px;
--leaderboard-height: 90px;
grid-column: 1/-1; grid-column: 1/-1;
margin: 24px 0;
overflow: hidden; overflow: hidden;
width: 100vw;
margin-inline-start: 50%;
transform: translate3d(-50%, 0, 0);
} }
.ad-banner-wrapper .ad-banner-dismiss { .ad-banner-wrapper .ad-banner-inner {
margin: 10px auto; 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; text-align: end;
margin-inline-end: 10px;
} }
.ad-banner-wrapper .ad-banner-dismiss .icon-dismiss { .ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss .icon-dismiss {
background-size: 20px; background-size: var(--size-item-small);
border: 0; border: 0;
cursor: pointer; cursor: pointer;
} }
.ad-banner-wrapper .ad-banner-sponsored { .ad-banner-wrapper .ad-banner-inner.leaderboard {
margin: 13px auto; 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; text-transform: uppercase;
font-size: var(--font-size-small); font-size: var(--font-size-small);
color: var(--newtab-contextual-text-secondary-color); 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.refreshTopicSelectionCache = this.refreshTopicSelectionCache.bind(this);
this.toggleTBRFeed = this.toggleTBRFeed.bind(this); this.toggleTBRFeed = this.toggleTBRFeed.bind(this);
this.handleSectionsToggle = this.handleSectionsToggle.bind(this); this.handleSectionsToggle = this.handleSectionsToggle.bind(this);
this.toggleIABBanners = this.toggleIABBanners.bind(this);
this.state = { this.state = {
toggledStories: {}, toggledStories: {},
weatherQuery: "" weatherQuery: ""
@@ -774,6 +775,37 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
} = this.state; } = this.state;
this.props.dispatch(actionCreators.SetPref("weather.query", weatherQuery)); 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) { handleSectionsToggle(e) {
const { const {
pressed pressed
@@ -928,6 +960,13 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
const selectedFeed = this.props.otherPrefs["discoverystream.contextualContent.selectedFeed"]; const selectedFeed = this.props.otherPrefs["discoverystream.contextualContent.selectedFeed"];
const sectionsEnabled = this.props.otherPrefs["discoverystream.sections.enabled"]; const sectionsEnabled = this.props.otherPrefs["discoverystream.sections.enabled"];
const TBRFeeds = this.props.otherPrefs["discoverystream.contextualContent.feeds"].split(",").map(s => s.trim()).filter(item => item); 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", { return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", {
className: "button", className: "button",
onClick: this.restorePrefDefaults onClick: this.restorePrefDefaults
@@ -966,7 +1005,23 @@ class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
pressed: sectionsEnabled || null, pressed: sectionsEnabled || null,
onToggle: this.handleSectionsToggle, onToggle: this.handleSectionsToggle,
label: "Toggle DS Sections" 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 key: pref
}, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, { }, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, {
checked: config[pref], checked: config[pref],
@@ -4110,7 +4165,8 @@ function ListFeed({
const AdBanner = ({ const AdBanner = ({
spoc, spoc,
dispatch, dispatch,
firstVisibleTimestamp firstVisibleTimestamp,
row
}) => { }) => {
const getDimensions = format => { const getDimensions = format => {
switch (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", { 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", { }, /*#__PURE__*/external_React_default().createElement("div", {
className: "ad-banner-dismiss" className: "ad-banner-dismiss"
}, /*#__PURE__*/external_React_default().createElement("button", { }, /*#__PURE__*/external_React_default().createElement("button", {
@@ -4185,7 +4250,7 @@ const AdBanner = ({
}, /*#__PURE__*/external_React_default().createElement("img", { }, /*#__PURE__*/external_React_default().createElement("img", {
src: spoc.raw_image_src, src: spoc.raw_image_src,
alt: spoc.alt_text, alt: spoc.alt_text,
loading: "lazy", loading: "eager",
width: imgWidth, width: imgWidth,
height: imgHeight height: imgHeight
}))), /*#__PURE__*/external_React_default().createElement("div", { }))), /*#__PURE__*/external_React_default().createElement("div", {
@@ -4193,7 +4258,7 @@ const AdBanner = ({
}, /*#__PURE__*/external_React_default().createElement("span", { }, /*#__PURE__*/external_React_default().createElement("span", {
className: "ad-banner-sponsored-label", className: "ad-banner-sponsored-label",
"data-l10n-id": "newtab-topsite-sponsored" "data-l10n-id": "newtab-topsite-sponsored"
}))); }))));
}; };
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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_ENABLED = "discoverystream.contextualContent.enabled";
const PREF_LIST_FEED_SELECTED_FEED = "discoverystream.contextualContent.selectedFeed"; const PREF_LIST_FEED_SELECTED_FEED = "discoverystream.contextualContent.selectedFeed";
const PREF_FAKESPOT_ENABLED = "discoverystream.contextualContent.fakespot.enabled"; 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_INTERSECTION_RATIO = 0.5;
const CardGrid_VISIBLE = "visible"; const CardGrid_VISIBLE = "visible";
const CardGrid_VISIBILITY_CHANGE_EVENT = "visibilitychange"; 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 spocsStartupCacheEnabled = prefs[PREF_SPOCS_STARTUPCACHE_ENABLED];
const listFeedEnabled = prefs[PREF_LIST_FEED_ENABLED]; const listFeedEnabled = prefs[PREF_LIST_FEED_ENABLED];
const listFeedSelectedFeed = prefs[PREF_LIST_FEED_SELECTED_FEED]; 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 // filter out recs that should be in ListFeed
const recs = this.props.data.recommendations.filter(item => !item.feedName).slice(0, items); const recs = this.props.data.recommendations.filter(item => !item.feedName).slice(0, items);
const cards = []; const cards = [];
@@ -4508,62 +4579,52 @@ class _CardGrid extends (external_React_default()).PureComponent {
let editorsPicksCards = []; let editorsPicksCards = [];
for (let index = 0; index < items; index++) { for (let index = 0; index < items; index++) {
const rec = recs[index]; const rec = recs[index];
if (rec?.format === "billboard" || rec?.format === "leaderboard") { cards.push(topicsLoading || !rec || rec.placeholder || rec.flight_id && !spocsStartupCacheEnabled && this.props.App.isForStartupCache ? /*#__PURE__*/external_React_default().createElement(PlaceholderDSCard, {
cards.push( /*#__PURE__*/external_React_default().createElement(AdBanner, { key: `dscard-${index}`
spoc: rec, }) : /*#__PURE__*/external_React_default().createElement(DSCard, {
key: `dscard-${rec.id}`, key: `dscard-${rec.id}`,
dispatch: this.props.dispatch, pos: rec.pos,
type: this.props.type, flightId: rec.flight_id,
firstVisibleTimestamp: this.props.firstVisibleTimestamp image_src: rec.image_src,
})); raw_image_src: rec.raw_image_src,
} else { word_count: rec.word_count,
cards.push(topicsLoading || !rec || rec.placeholder || rec.flight_id && !spocsStartupCacheEnabled && this.props.App.isForStartupCache ? /*#__PURE__*/external_React_default().createElement(PlaceholderDSCard, { time_to_read: rec.time_to_read,
key: `dscard-${index}` title: rec.title,
}) : /*#__PURE__*/external_React_default().createElement(DSCard, { topic: rec.topic,
key: `dscard-${rec.id}`, showTopics: showTopics,
pos: rec.pos, selectedTopics: selectedTopics,
flightId: rec.flight_id, availableTopics: availableTopics,
image_src: rec.image_src, excerpt: rec.excerpt,
raw_image_src: rec.raw_image_src, url: rec.url,
word_count: rec.word_count, id: rec.id,
time_to_read: rec.time_to_read, shim: rec.shim,
title: rec.title, fetchTimestamp: rec.fetchTimestamp,
topic: rec.topic, type: this.props.type,
showTopics: showTopics, context: rec.context,
selectedTopics: selectedTopics, sponsor: rec.sponsor,
availableTopics: availableTopics, sponsored_by_override: rec.sponsored_by_override,
excerpt: rec.excerpt, dispatch: this.props.dispatch,
url: rec.url, source: rec.domain,
id: rec.id, publisher: rec.publisher,
shim: rec.shim, pocket_id: rec.pocket_id,
fetchTimestamp: rec.fetchTimestamp, context_type: rec.context_type,
type: this.props.type, bookmarkGuid: rec.bookmarkGuid,
context: rec.context, is_collection: this.props.is_collection,
sponsor: rec.sponsor, saveToPocketCard: saveToPocketCard,
sponsored_by_override: rec.sponsored_by_override, ctaButtonSponsors: ctaButtonSponsors,
dispatch: this.props.dispatch, ctaButtonVariant: ctaButtonVariant,
source: rec.domain, spocMessageVariant: spocMessageVariant,
publisher: rec.publisher, recommendation_id: rec.recommendation_id,
pocket_id: rec.pocket_id, firstVisibleTimestamp: this.props.firstVisibleTimestamp,
context_type: rec.context_type, mayHaveThumbsUpDown: mayHaveThumbsUpDown,
bookmarkGuid: rec.bookmarkGuid, mayHaveSectionsCards: mayHaveSectionsCards,
is_collection: this.props.is_collection, corpus_item_id: rec.corpus_item_id,
saveToPocketCard: saveToPocketCard, scheduled_corpus_item_id: rec.scheduled_corpus_item_id,
ctaButtonSponsors: ctaButtonSponsors, recommended_at: rec.recommended_at,
ctaButtonVariant: ctaButtonVariant, received_rank: rec.received_rank,
spocMessageVariant: spocMessageVariant, format: rec.format,
recommendation_id: rec.recommendation_id, alt_text: rec.alt_text
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) { if (widgets?.positions?.length && widgets?.data?.length) {
let positionIndex = 0; 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)); 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 = ""; let moreRecsHeader = "";
// For now this is English only. // For now this is English only.
if (showRecentSaves || essentialReadsHeader && editorsPicksHeader) { if (showRecentSaves || essentialReadsHeader && editorsPicksHeader) {
@@ -9372,8 +9459,11 @@ const selectLayoutRender = ({ state = {}, prefs = {} }) => {
const results = [...data]; const results = [...data];
for (let position of spocsPositions) { for (let position of spocsPositions) {
const spoc = spocsData[spocIndexPlacementMap[placementName]]; const spoc = spocsData[spocIndexPlacementMap[placementName]];
const format = spoc?.format;
// If there are no spocs left, we can stop filling positions. // 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; break;
} }

View File

@@ -436,6 +436,14 @@ export const PREFS_CONFIG = new Map([
value: false, 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", "newtabAdSize.billboard",
{ {
@@ -443,6 +451,14 @@ export const PREFS_CONFIG = new Map([
value: false, 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", "newtabLayouts.variant-a",
{ {

View File

@@ -858,6 +858,13 @@ newtabAdSizingExperiment:
pref: browser.newtabpage.activity-stream.newtabAdSize.leaderboard pref: browser.newtabpage.activity-stream.newtabAdSize.leaderboard
description: >- description: >-
Leaderboard ad size and UI. 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: billboard:
type: boolean type: boolean
setPref: setPref:
@@ -865,6 +872,14 @@ newtabAdSizingExperiment:
pref: browser.newtabpage.activity-stream.newtabAdSize.billboard pref: browser.newtabpage.activity-stream.newtabAdSize.billboard
description: >- description: >-
Billboard ad size and UI. 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: newtabLayoutExperiment:
description: >- description: >-
Change the default layout of new tab by adjusting sizes and spacing of elements. Change the default layout of new tab by adjusting sizes and spacing of elements.