summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/content-src/components/TopSites/TopSites.jsx')
-rw-r--r--browser/components/newtab/content-src/components/TopSites/TopSites.jsx213
1 files changed, 213 insertions, 0 deletions
diff --git a/browser/components/newtab/content-src/components/TopSites/TopSites.jsx b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
new file mode 100644
index 0000000000..fd1e3048a5
--- /dev/null
+++ b/browser/components/newtab/content-src/components/TopSites/TopSites.jsx
@@ -0,0 +1,213 @@
+/* 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 {
+ actionCreators as ac,
+ actionTypes as at,
+} from "common/Actions.sys.mjs";
+import { MIN_RICH_FAVICON_SIZE, TOP_SITES_SOURCE } from "./TopSitesConstants";
+import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection";
+import { ComponentPerfTimer } from "content-src/components/ComponentPerfTimer/ComponentPerfTimer";
+import { connect } from "react-redux";
+import { ModalOverlayWrapper } from "../../asrouter/components/ModalOverlay/ModalOverlay";
+import React from "react";
+import { SearchShortcutsForm } from "./SearchShortcutsForm";
+import { TOP_SITES_MAX_SITES_PER_ROW } from "common/Reducers.sys.mjs";
+import { TopSiteForm } from "./TopSiteForm";
+import { TopSiteList } from "./TopSite";
+
+function topSiteIconType(link) {
+ if (link.customScreenshotURL) {
+ return "custom_screenshot";
+ }
+ if (link.tippyTopIcon || link.faviconRef === "tippytop") {
+ return "tippytop";
+ }
+ if (link.faviconSize >= MIN_RICH_FAVICON_SIZE) {
+ return "rich_icon";
+ }
+ if (link.screenshot) {
+ return "screenshot";
+ }
+ return "no_image";
+}
+
+/**
+ * Iterates through TopSites and counts types of images.
+ * @param acc Accumulator for reducer.
+ * @param topsite Entry in TopSites.
+ */
+function countTopSitesIconsTypes(topSites) {
+ const countTopSitesTypes = (acc, link) => {
+ acc[topSiteIconType(link)]++;
+ return acc;
+ };
+
+ return topSites.reduce(countTopSitesTypes, {
+ custom_screenshot: 0,
+ screenshot: 0,
+ tippytop: 0,
+ rich_icon: 0,
+ no_image: 0,
+ });
+}
+
+export class _TopSites extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onEditFormClose = this.onEditFormClose.bind(this);
+ this.onSearchShortcutsFormClose =
+ this.onSearchShortcutsFormClose.bind(this);
+ }
+
+ /**
+ * Dispatch session statistics about the quality of TopSites icons and pinned count.
+ */
+ _dispatchTopSitesStats() {
+ const topSites = this._getVisibleTopSites().filter(
+ topSite => topSite !== null && topSite !== undefined
+ );
+ const topSitesIconsStats = countTopSitesIconsTypes(topSites);
+ const topSitesPinned = topSites.filter(site => !!site.isPinned).length;
+ const searchShortcuts = topSites.filter(
+ site => !!site.searchTopSite
+ ).length;
+ // Dispatch telemetry event with the count of TopSites images types.
+ this.props.dispatch(
+ ac.AlsoToMain({
+ type: at.SAVE_SESSION_PERF_DATA,
+ data: {
+ topsites_icon_stats: topSitesIconsStats,
+ topsites_pinned: topSitesPinned,
+ topsites_search_shortcuts: searchShortcuts,
+ },
+ })
+ );
+ }
+
+ /**
+ * Return the TopSites that are visible based on prefs and window width.
+ */
+ _getVisibleTopSites() {
+ // We hide 2 sites per row when not in the wide layout.
+ let sitesPerRow = TOP_SITES_MAX_SITES_PER_ROW;
+ // $break-point-widest = 1072px (from _variables.scss)
+ if (!global.matchMedia(`(min-width: 1072px)`).matches) {
+ sitesPerRow -= 2;
+ }
+ return this.props.TopSites.rows.slice(
+ 0,
+ this.props.TopSitesRows * sitesPerRow
+ );
+ }
+
+ componentDidUpdate() {
+ this._dispatchTopSitesStats();
+ }
+
+ componentDidMount() {
+ this._dispatchTopSitesStats();
+ }
+
+ onEditFormClose() {
+ this.props.dispatch(
+ ac.UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "TOP_SITES_EDIT_CLOSE",
+ })
+ );
+ this.props.dispatch({ type: at.TOP_SITES_CANCEL_EDIT });
+ }
+
+ onSearchShortcutsFormClose() {
+ this.props.dispatch(
+ ac.UserEvent({
+ source: TOP_SITES_SOURCE,
+ event: "SEARCH_EDIT_CLOSE",
+ })
+ );
+ this.props.dispatch({ type: at.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL });
+ }
+
+ render() {
+ const { props } = this;
+ const { editForm, showSearchShortcutsForm } = props.TopSites;
+ const extraMenuOptions = ["AddTopSite"];
+ const colors = props.Prefs.values["newNewtabExperience.colors"];
+
+ if (props.Prefs.values["improvesearch.topSiteSearchShortcuts"]) {
+ extraMenuOptions.push("AddSearchShortcut");
+ }
+
+ return (
+ <ComponentPerfTimer
+ id="topsites"
+ initialized={props.TopSites.initialized}
+ dispatch={props.dispatch}
+ >
+ <CollapsibleSection
+ className="top-sites"
+ id="topsites"
+ title={props.title || { id: "newtab-section-header-topsites" }}
+ hideTitle={true}
+ extraMenuOptions={extraMenuOptions}
+ showPrefName="feeds.topsites"
+ eventSource={TOP_SITES_SOURCE}
+ collapsed={false}
+ isFixed={props.isFixed}
+ isFirst={props.isFirst}
+ isLast={props.isLast}
+ dispatch={props.dispatch}
+ >
+ <TopSiteList
+ TopSites={props.TopSites}
+ TopSitesRows={props.TopSitesRows}
+ dispatch={props.dispatch}
+ topSiteIconType={topSiteIconType}
+ colors={colors}
+ />
+ <div className="edit-topsites-wrapper">
+ {editForm && (
+ <div className="edit-topsites">
+ <ModalOverlayWrapper
+ unstyled={true}
+ onClose={this.onEditFormClose}
+ innerClassName="modal"
+ >
+ <TopSiteForm
+ site={props.TopSites.rows[editForm.index]}
+ onClose={this.onEditFormClose}
+ dispatch={this.props.dispatch}
+ {...editForm}
+ />
+ </ModalOverlayWrapper>
+ </div>
+ )}
+ {showSearchShortcutsForm && (
+ <div className="edit-search-shortcuts">
+ <ModalOverlayWrapper
+ unstyled={true}
+ onClose={this.onSearchShortcutsFormClose}
+ innerClassName="modal"
+ >
+ <SearchShortcutsForm
+ TopSites={props.TopSites}
+ onClose={this.onSearchShortcutsFormClose}
+ dispatch={this.props.dispatch}
+ />
+ </ModalOverlayWrapper>
+ </div>
+ )}
+ </div>
+ </CollapsibleSection>
+ </ComponentPerfTimer>
+ );
+ }
+}
+
+export const TopSites = connect((state, props) => ({
+ TopSites: state.TopSites,
+ Prefs: state.Prefs,
+ TopSitesRows: state.Prefs.values.topSitesRows,
+}))(_TopSites);