Source code

Revision control

Copy as Markdown

Other Tools

/* 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 {
actionTypes as at,
actionCreators as ac,
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
const SECTION_LAYOUTS_REMOTE_SETTINGS_COLLECTION = "newtab-section-layouts";
const PREF_CLIENT_LAYOUT_ENABLED =
"discoverystream.sections.clientLayout.enabled";
const PREF_TOPSTORIES_ENABLED = "feeds.section.topstories";
const DEFAULT_SECTION_LAYOUT = [
{
name: "6-small-medium-1-ad",
responsiveLayouts: [
{
columnCount: 4,
tiles: [
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 4,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 5,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 3,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 2,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 3,
hasAd: true,
hasExcerpt: true,
},
{
size: "medium",
position: 4,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 5,
hasAd: false,
hasExcerpt: true,
},
],
},
{
columnCount: 2,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 4,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 5,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 1,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 4,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 5,
hasAd: false,
hasExcerpt: false,
},
],
},
],
},
{
name: "4-large-small-medium-1-ad",
responsiveLayouts: [
{
columnCount: 4,
tiles: [
{
size: "large",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 3,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 2,
tiles: [
{
size: "large",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 1,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
],
},
{
name: "4-medium-small-1-ad",
responsiveLayouts: [
{
columnCount: 4,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 2,
hasAd: false,
hasExcerpt: true,
},
{ size: "medium", position: 3, hasAd: true, hasExcerpt: true },
],
},
{
columnCount: 3,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 2,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
{
columnCount: 1,
tiles: [
{
size: "medium",
position: 0,
hasAd: false,
hasExcerpt: true,
},
{
size: "medium",
position: 1,
hasAd: true,
hasExcerpt: true,
},
{
size: "small",
position: 2,
hasAd: false,
hasExcerpt: false,
},
{
size: "small",
position: 3,
hasAd: false,
hasExcerpt: false,
},
],
},
],
},
];
export const SectionsLayoutManager = {
DEFAULT_SECTION_LAYOUT,
};
function isValidLayout(record) {
if (!record.name || !Array.isArray(record.responsiveLayouts)) {
return false;
}
const columnCounts = new Set(
record.responsiveLayouts.map(layout => layout.columnCount)
);
return [1, 2, 3, 4].every(n => columnCounts.has(n));
}
export class SectionsLayoutFeed {
constructor() {
this._rsClient = null;
this._onSync = this._onSync.bind(this);
}
RemoteSettings(...args) {
return lazy.RemoteSettings(...args);
}
async init(isStartup = false) {
const prefs = this.store.getState().Prefs.values;
const useClientLayout =
prefs[PREF_TOPSTORIES_ENABLED] &&
(prefs.trainhopConfig?.clientLayout?.enabled ||
prefs[PREF_CLIENT_LAYOUT_ENABLED]);
if (useClientLayout) {
if (!this._rsClient) {
this._rsClient = this.RemoteSettings(
SECTION_LAYOUTS_REMOTE_SETTINGS_COLLECTION
);
this._rsClient.on("sync", this._onSync);
}
await this._fetchLayouts(isStartup);
}
}
uninit() {
if (this._rsClient) {
this._rsClient.off("sync", this._onSync);
this._rsClient = null;
}
}
async _onSync() {
await this._fetchLayouts(false);
}
async _fetchLayouts(isStartup) {
const records = await this._rsClient.get();
if (!records?.length) {
return;
}
const configs = {};
for (const record of records) {
if (isValidLayout(record)) {
configs[record.name] = record;
}
}
if (!Object.keys(configs).length) {
return;
}
this.store.dispatch(
ac.BroadcastToContent({
type: at.SECTIONS_LAYOUT_UPDATE,
data: { configs },
meta: { isStartup },
})
);
}
async onAction(action) {
switch (action.type) {
case at.INIT:
await this.init(true);
break;
case at.UNINIT:
this.uninit();
break;
case at.PREF_CHANGED:
if (
action.data.name === PREF_CLIENT_LAYOUT_ENABLED ||
action.data.name === PREF_TOPSTORIES_ENABLED ||
(action.data.name === "trainhopConfig" &&
action.data.value?.clientLayout)
) {
await this.init(false);
}
break;
}
}
}