зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1767445 - Pocket newtab topics widget r=gvn
Differential Revision: https://phabricator.services.mozilla.com/D146327
This commit is contained in:
Родитель
7c5eeccac8
Коммит
49ba6a6e01
|
@ -1552,6 +1552,7 @@ pref("browser.newtabpage.activity-stream.discoverystream.newSponsoredLabel.enabl
|
||||||
pref("browser.newtabpage.activity-stream.discoverystream.essentialReadsHeader.enabled", false);
|
pref("browser.newtabpage.activity-stream.discoverystream.essentialReadsHeader.enabled", false);
|
||||||
pref("browser.newtabpage.activity-stream.discoverystream.editorsPicksHeader.enabled", false);
|
pref("browser.newtabpage.activity-stream.discoverystream.editorsPicksHeader.enabled", false);
|
||||||
pref("browser.newtabpage.activity-stream.discoverystream.spoc-positions", "1,5,7,11,18,20");
|
pref("browser.newtabpage.activity-stream.discoverystream.spoc-positions", "1,5,7,11,18,20");
|
||||||
|
pref("browser.newtabpage.activity-stream.discoverystream.widget-positions", "");
|
||||||
|
|
||||||
pref("browser.newtabpage.activity-stream.discoverystream.spocs-endpoint", "");
|
pref("browser.newtabpage.activity-stream.discoverystream.spocs-endpoint", "");
|
||||||
pref("browser.newtabpage.activity-stream.discoverystream.spocs-endpoint-query", "");
|
pref("browser.newtabpage.activity-stream.discoverystream.spocs-endpoint-query", "");
|
||||||
|
|
|
@ -207,6 +207,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
||||||
display_variant={component.properties.display_variant}
|
display_variant={component.properties.display_variant}
|
||||||
data={component.data}
|
data={component.data}
|
||||||
feed={component.feed}
|
feed={component.feed}
|
||||||
|
widgets={component.widgets}
|
||||||
border={component.properties.border}
|
border={component.properties.border}
|
||||||
type={component.type}
|
type={component.type}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
LastCardMessage,
|
LastCardMessage,
|
||||||
} from "../DSCard/DSCard.jsx";
|
} from "../DSCard/DSCard.jsx";
|
||||||
import { DSEmptyState } from "../DSEmptyState/DSEmptyState.jsx";
|
import { DSEmptyState } from "../DSEmptyState/DSEmptyState.jsx";
|
||||||
|
import { TopicsWidget } from "../TopicsWidget/TopicsWidget.jsx";
|
||||||
import { FluentOrText } from "../../FluentOrText/FluentOrText.jsx";
|
import { FluentOrText } from "../../FluentOrText/FluentOrText.jsx";
|
||||||
import { actionCreators as ac } from "common/Actions.jsm";
|
import { actionCreators as ac } from "common/Actions.jsm";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -69,6 +70,7 @@ export class CardGrid extends React.PureComponent {
|
||||||
readTime,
|
readTime,
|
||||||
essentialReadsHeader,
|
essentialReadsHeader,
|
||||||
editorsPicksHeader,
|
editorsPicksHeader,
|
||||||
|
widgets,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let showLastCardMessage = lastCardMessageEnabled;
|
let showLastCardMessage = lastCardMessageEnabled;
|
||||||
if (this.showLoadMore) {
|
if (this.showLoadMore) {
|
||||||
|
@ -148,6 +150,33 @@ export class CardGrid extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widgets?.positions?.length && widgets?.data?.length) {
|
||||||
|
let positionIndex = 0;
|
||||||
|
|
||||||
|
for (const widget of widgets.data) {
|
||||||
|
let widgetComponent = null;
|
||||||
|
const position = widgets.positions[positionIndex];
|
||||||
|
|
||||||
|
// Stop if we run out of positions to place widgets.
|
||||||
|
if (!position) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (widget?.type) {
|
||||||
|
case "TopicsWidget":
|
||||||
|
widgetComponent = <TopicsWidget />;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgetComponent) {
|
||||||
|
// We found a widget, so up the position for next try.
|
||||||
|
positionIndex++;
|
||||||
|
// We replace an existing card with the widget.
|
||||||
|
cards.splice(position.index, 1, widgetComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Used for CSS overrides to default styling (eg: "hero")
|
// Used for CSS overrides to default styling (eg: "hero")
|
||||||
const variantClass = this.props.display_variant
|
const variantClass = this.props.display_variant
|
||||||
? `ds-card-grid-${this.props.display_variant}`
|
? `ds-card-grid-${this.props.display_variant}`
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* 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 React from "react";
|
||||||
|
|
||||||
|
export function TopicsWidget(props) {
|
||||||
|
return <div className="ds-topics-widget"></div>;
|
||||||
|
}
|
|
@ -167,6 +167,7 @@ input {
|
||||||
@import '../components/DiscoveryStreamComponents/DSSignup/DSSignup';
|
@import '../components/DiscoveryStreamComponents/DSSignup/DSSignup';
|
||||||
@import '../components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal';
|
@import '../components/DiscoveryStreamComponents/DSPrivacyModal/DSPrivacyModal';
|
||||||
@import '../components/DiscoveryStreamComponents/PrivacyLink/PrivacyLink';
|
@import '../components/DiscoveryStreamComponents/PrivacyLink/PrivacyLink';
|
||||||
|
@import '../components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget';
|
||||||
|
|
||||||
// AS Router
|
// AS Router
|
||||||
@import '../asrouter/components/Button/Button';
|
@import '../asrouter/components/Button/Button';
|
||||||
|
|
|
@ -8009,6 +8009,16 @@ class DSEmptyState extends (external_React_default()).PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
function TopicsWidget(props) {
|
||||||
|
return /*#__PURE__*/external_React_default().createElement("div", {
|
||||||
|
className: "ds-topics-widget"
|
||||||
|
});
|
||||||
|
}
|
||||||
;// 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
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
@ -8018,6 +8028,7 @@ class DSEmptyState extends (external_React_default()).PureComponent {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CardGrid extends (external_React_default()).PureComponent {
|
class CardGrid extends (external_React_default()).PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -8059,6 +8070,8 @@ class CardGrid extends (external_React_default()).PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCards() {
|
renderCards() {
|
||||||
|
var _widgets$positions, _widgets$data;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
items
|
items
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -8078,7 +8091,8 @@ class CardGrid extends (external_React_default()).PureComponent {
|
||||||
descLines,
|
descLines,
|
||||||
readTime,
|
readTime,
|
||||||
essentialReadsHeader,
|
essentialReadsHeader,
|
||||||
editorsPicksHeader
|
editorsPicksHeader,
|
||||||
|
widgets
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let showLastCardMessage = lastCardMessageEnabled;
|
let showLastCardMessage = lastCardMessageEnabled;
|
||||||
|
|
||||||
|
@ -8151,6 +8165,32 @@ class CardGrid extends (external_React_default()).PureComponent {
|
||||||
cards.splice(cards.length - 1, 1, /*#__PURE__*/external_React_default().createElement(LastCardMessage, {
|
cards.splice(cards.length - 1, 1, /*#__PURE__*/external_React_default().createElement(LastCardMessage, {
|
||||||
key: `dscard-last-${cards.length - 1}`
|
key: `dscard-last-${cards.length - 1}`
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgets !== null && widgets !== void 0 && (_widgets$positions = widgets.positions) !== null && _widgets$positions !== void 0 && _widgets$positions.length && widgets !== null && widgets !== void 0 && (_widgets$data = widgets.data) !== null && _widgets$data !== void 0 && _widgets$data.length) {
|
||||||
|
let positionIndex = 0;
|
||||||
|
|
||||||
|
for (const widget of widgets.data) {
|
||||||
|
let widgetComponent = null;
|
||||||
|
const position = widgets.positions[positionIndex]; // Stop if we run out of positions to place widgets.
|
||||||
|
|
||||||
|
if (!position) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (widget === null || widget === void 0 ? void 0 : widget.type) {
|
||||||
|
case "TopicsWidget":
|
||||||
|
widgetComponent = /*#__PURE__*/external_React_default().createElement(TopicsWidget, null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgetComponent) {
|
||||||
|
// We found a widget, so up the position for next try.
|
||||||
|
positionIndex++; // We replace an existing card with the widget.
|
||||||
|
|
||||||
|
cards.splice(position.index, 1, widgetComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
} // Used for CSS overrides to default styling (eg: "hero")
|
} // Used for CSS overrides to default styling (eg: "hero")
|
||||||
|
|
||||||
|
|
||||||
|
@ -13531,6 +13571,7 @@ class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
|
||||||
display_variant: component.properties.display_variant,
|
display_variant: component.properties.display_variant,
|
||||||
data: component.data,
|
data: component.data,
|
||||||
feed: component.feed,
|
feed: component.feed,
|
||||||
|
widgets: component.widgets,
|
||||||
border: component.properties.border,
|
border: component.properties.border,
|
||||||
type: component.type,
|
type: component.type,
|
||||||
dispatch: this.props.dispatch,
|
dispatch: this.props.dispatch,
|
||||||
|
|
|
@ -418,12 +418,12 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
||||||
return urlObject.toString();
|
return urlObject.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSpocPositions(csvPositions) {
|
parseGridPositions(csvPositions) {
|
||||||
let spocPositions;
|
let gridPositions;
|
||||||
|
|
||||||
// Only accept parseable non-negative integers
|
// Only accept parseable non-negative integers
|
||||||
try {
|
try {
|
||||||
spocPositions = csvPositions.map(index => {
|
gridPositions = csvPositions.map(index => {
|
||||||
let parsedInt = parseInt(index, 10);
|
let parsedInt = parseInt(index, 10);
|
||||||
|
|
||||||
if (!isNaN(parsedInt) && parsedInt >= 0) {
|
if (!isNaN(parsedInt) && parsedInt >= 0) {
|
||||||
|
@ -435,10 +435,10 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Catch spoc positions that are not numbers or negative, and do nothing.
|
// Catch spoc positions that are not numbers or negative, and do nothing.
|
||||||
// We have hard coded backup positions.
|
// We have hard coded backup positions.
|
||||||
spocPositions = undefined;
|
gridPositions = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return spocPositions;
|
return gridPositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadLayout(sendUpdate, isStartup) {
|
async loadLayout(sendUpdate, isStartup) {
|
||||||
|
@ -480,9 +480,15 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
||||||
layoutResp = getHardcodedLayout({
|
layoutResp = getHardcodedLayout({
|
||||||
items,
|
items,
|
||||||
sponsoredCollectionsEnabled,
|
sponsoredCollectionsEnabled,
|
||||||
spocPositions: this.parseSpocPositions(
|
spocPositions: this.parseGridPositions(
|
||||||
pocketConfig.spocPositions?.split(`,`)
|
pocketConfig.spocPositions?.split(`,`)
|
||||||
),
|
),
|
||||||
|
widgetPositions: this.parseGridPositions(
|
||||||
|
pocketConfig.widgetPositions?.split(`,`)
|
||||||
|
),
|
||||||
|
widgetData: [
|
||||||
|
...(this.locale.startsWith("en-") ? [{ type: "TopicsWidget" }] : []),
|
||||||
|
],
|
||||||
compactLayout: pocketConfig.compactLayout,
|
compactLayout: pocketConfig.compactLayout,
|
||||||
hybridLayout: pocketConfig.hybridLayout,
|
hybridLayout: pocketConfig.hybridLayout,
|
||||||
hideCardBackground: pocketConfig.hideCardBackground,
|
hideCardBackground: pocketConfig.hideCardBackground,
|
||||||
|
@ -1906,6 +1912,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
|
||||||
getHardcodedLayout = ({
|
getHardcodedLayout = ({
|
||||||
items = 21,
|
items = 21,
|
||||||
spocPositions = [1, 5, 7, 11, 18, 20],
|
spocPositions = [1, 5, 7, 11, 18, 20],
|
||||||
|
widgetPositions = [],
|
||||||
|
widgetData = [],
|
||||||
sponsoredCollectionsEnabled = false,
|
sponsoredCollectionsEnabled = false,
|
||||||
compactLayout = false,
|
compactLayout = false,
|
||||||
hybridLayout = false,
|
hybridLayout = false,
|
||||||
|
@ -2016,6 +2024,12 @@ getHardcodedLayout = ({
|
||||||
editorsPicksHeader,
|
editorsPicksHeader,
|
||||||
readTime: readTime || compactLayout,
|
readTime: readTime || compactLayout,
|
||||||
},
|
},
|
||||||
|
widgets: {
|
||||||
|
positions: widgetPositions.map(position => {
|
||||||
|
return { index: position };
|
||||||
|
}),
|
||||||
|
data: widgetData,
|
||||||
|
},
|
||||||
loadMore,
|
loadMore,
|
||||||
lastCardMessageEnabled,
|
lastCardMessageEnabled,
|
||||||
pocketButtonEnabled,
|
pocketButtonEnabled,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
DSCard,
|
DSCard,
|
||||||
LastCardMessage,
|
LastCardMessage,
|
||||||
} from "content-src/components/DiscoveryStreamComponents/DSCard/DSCard";
|
} from "content-src/components/DiscoveryStreamComponents/DSCard/DSCard";
|
||||||
|
import { TopicsWidget } from "content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget";
|
||||||
import { actionCreators as ac } from "common/Actions.jsm";
|
import { actionCreators as ac } from "common/Actions.jsm";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
@ -140,4 +141,18 @@ describe("<CardGrid>", () => {
|
||||||
loadMoreButton = wrapper.find(".ds-card-grid-load-more-button");
|
loadMoreButton = wrapper.find(".ds-card-grid-load-more-button");
|
||||||
assert.ok(!loadMoreButton.exists());
|
assert.ok(!loadMoreButton.exists());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create a widget card", () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
widgets: {
|
||||||
|
positions: [{ index: 1 }],
|
||||||
|
data: [{ type: "TopicsWidget" }],
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
recommendations: [{}, {}, {}],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(wrapper.find(TopicsWidget).exists());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { TopicsWidget } from "content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget";
|
||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
describe("Discovery Stream <TopicsWidget>", () => {
|
||||||
|
let sandbox;
|
||||||
|
let wrapper;
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox();
|
||||||
|
wrapper = shallow(<TopicsWidget />);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render", () => {
|
||||||
|
assert.ok(wrapper.exists());
|
||||||
|
assert.ok(wrapper.find(".ds-topics-widget"));
|
||||||
|
});
|
||||||
|
});
|
|
@ -226,15 +226,15 @@ describe("DiscoveryStreamFeed", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#parseSpocPositions", () => {
|
describe("#parseGridPositions", () => {
|
||||||
it("should return an equivalent array for an array of non negative integers", async () => {
|
it("should return an equivalent array for an array of non negative integers", async () => {
|
||||||
assert.deepEqual(feed.parseSpocPositions([0, 2, 3]), [0, 2, 3]);
|
assert.deepEqual(feed.parseGridPositions([0, 2, 3]), [0, 2, 3]);
|
||||||
});
|
});
|
||||||
it("should return undefined for an array containing negative integers", async () => {
|
it("should return undefined for an array containing negative integers", async () => {
|
||||||
assert.equal(feed.parseSpocPositions([-2, 2, 3]), undefined);
|
assert.equal(feed.parseGridPositions([-2, 2, 3]), undefined);
|
||||||
});
|
});
|
||||||
it("should return undefined for an undefined input", async () => {
|
it("should return undefined for an undefined input", async () => {
|
||||||
assert.equal(feed.parseSpocPositions(undefined), undefined);
|
assert.equal(feed.parseGridPositions(undefined), undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -466,6 +466,31 @@ describe("DiscoveryStreamFeed", () => {
|
||||||
const { layout } = feed.store.getState().DiscoveryStream;
|
const { layout } = feed.store.getState().DiscoveryStream;
|
||||||
assert.equal(layout[0].components[2].properties.items, 24);
|
assert.equal(layout[0].components[2].properties.items, 24);
|
||||||
});
|
});
|
||||||
|
it("should create a layout with spoc and widget positions", async () => {
|
||||||
|
feed.config.hardcoded_layout = true;
|
||||||
|
feed.store = createStore(combineReducers(reducers), {
|
||||||
|
Prefs: {
|
||||||
|
values: {
|
||||||
|
pocketConfig: {
|
||||||
|
spocPositions: "1, 2",
|
||||||
|
widgetPositions: "3, 4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await feed.loadLayout(feed.store.dispatch);
|
||||||
|
|
||||||
|
const { layout } = feed.store.getState().DiscoveryStream;
|
||||||
|
assert.deepEqual(layout[0].components[2].spocs.positions, [
|
||||||
|
{ index: 1 },
|
||||||
|
{ index: 2 },
|
||||||
|
]);
|
||||||
|
assert.deepEqual(layout[0].components[2].widgets.positions, [
|
||||||
|
{ index: 3 },
|
||||||
|
{ index: 4 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#updatePlacements", () => {
|
describe("#updatePlacements", () => {
|
||||||
|
|
|
@ -234,6 +234,10 @@ pocketNewtab:
|
||||||
type: string
|
type: string
|
||||||
fallbackPref: browser.newtabpage.activity-stream.discoverystream.spoc-positions
|
fallbackPref: browser.newtabpage.activity-stream.discoverystream.spoc-positions
|
||||||
description: CSV string of spoc position indexes on newtab grid
|
description: CSV string of spoc position indexes on newtab grid
|
||||||
|
widgetPositions:
|
||||||
|
type: string
|
||||||
|
fallbackPref: browser.newtabpage.activity-stream.discoverystream.widget-positions
|
||||||
|
description: CSV string of widget position indexes on newtab grid
|
||||||
compactLayout:
|
compactLayout:
|
||||||
type: boolean
|
type: boolean
|
||||||
fallbackPref: browser.newtabpage.activity-stream.discoverystream.compactLayout.enabled
|
fallbackPref: browser.newtabpage.activity-stream.discoverystream.compactLayout.enabled
|
||||||
|
|
Загрузка…
Ссылка в новой задаче