tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TopSites.jsx (6837B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
      6 import { MIN_RICH_FAVICON_SIZE, TOP_SITES_SOURCE } from "./TopSitesConstants";
      7 import { CollapsibleSection } from "content-src/components/CollapsibleSection/CollapsibleSection";
      8 import { ComponentPerfTimer } from "content-src/components/ComponentPerfTimer/ComponentPerfTimer";
      9 import { connect } from "react-redux";
     10 import { ModalOverlayWrapper } from "content-src/components/ModalOverlay/ModalOverlay";
     11 import React from "react";
     12 import { SearchShortcutsForm } from "./SearchShortcutsForm";
     13 import { TOP_SITES_MAX_SITES_PER_ROW } from "common/Reducers.sys.mjs";
     14 import { TopSiteForm } from "./TopSiteForm";
     15 import { TopSiteList } from "./TopSite";
     16 
     17 function topSiteIconType(link) {
     18  if (link.customScreenshotURL) {
     19    return "custom_screenshot";
     20  }
     21  if (link.tippyTopIcon || link.faviconRef === "tippytop") {
     22    return "tippytop";
     23  }
     24  if (link.faviconSize >= MIN_RICH_FAVICON_SIZE) {
     25    return "rich_icon";
     26  }
     27  if (link.screenshot) {
     28    return "screenshot";
     29  }
     30  return "no_image";
     31 }
     32 
     33 /**
     34 * Iterates through TopSites and counts types of images.
     35 *
     36 * @param acc Accumulator for reducer.
     37 * @param topsite Entry in TopSites.
     38 */
     39 function countTopSitesIconsTypes(topSites) {
     40  const countTopSitesTypes = (acc, link) => {
     41    acc[topSiteIconType(link)]++;
     42    return acc;
     43  };
     44 
     45  return topSites.reduce(countTopSitesTypes, {
     46    custom_screenshot: 0,
     47    screenshot: 0,
     48    tippytop: 0,
     49    rich_icon: 0,
     50    no_image: 0,
     51  });
     52 }
     53 
     54 export class _TopSites extends React.PureComponent {
     55  constructor(props) {
     56    super(props);
     57    this.onEditFormClose = this.onEditFormClose.bind(this);
     58    this.onSearchShortcutsFormClose =
     59      this.onSearchShortcutsFormClose.bind(this);
     60  }
     61 
     62  /**
     63   * Dispatch session statistics about the quality of TopSites icons and pinned count.
     64   */
     65  _dispatchTopSitesStats() {
     66    const topSites = this._getVisibleTopSites().filter(
     67      topSite => topSite !== null && topSite !== undefined
     68    );
     69    const topSitesIconsStats = countTopSitesIconsTypes(topSites);
     70    const topSitesPinned = topSites.filter(site => !!site.isPinned).length;
     71    const searchShortcuts = topSites.filter(
     72      site => !!site.searchTopSite
     73    ).length;
     74    // Dispatch telemetry event with the count of TopSites images types.
     75    this.props.dispatch(
     76      ac.AlsoToMain({
     77        type: at.SAVE_SESSION_PERF_DATA,
     78        data: {
     79          topsites_icon_stats: topSitesIconsStats,
     80          topsites_pinned: topSitesPinned,
     81          topsites_search_shortcuts: searchShortcuts,
     82        },
     83      })
     84    );
     85  }
     86 
     87  /**
     88   * Return the TopSites that are visible based on prefs and window width.
     89   */
     90  _getVisibleTopSites() {
     91    // We hide 2 sites per row when not in the wide layout.
     92    let sitesPerRow = TOP_SITES_MAX_SITES_PER_ROW;
     93    // $break-point-widest = 1072px (from _variables.scss)
     94    if (!globalThis.matchMedia(`(min-width: 1072px)`).matches) {
     95      sitesPerRow -= 2;
     96    }
     97    return this.props.TopSites.rows.slice(
     98      0,
     99      this.props.TopSitesRows * sitesPerRow
    100    );
    101  }
    102 
    103  componentDidUpdate() {
    104    this._dispatchTopSitesStats();
    105  }
    106 
    107  componentDidMount() {
    108    this._dispatchTopSitesStats();
    109  }
    110 
    111  onEditFormClose() {
    112    this.props.dispatch(
    113      ac.UserEvent({
    114        source: TOP_SITES_SOURCE,
    115        event: "TOP_SITES_EDIT_CLOSE",
    116      })
    117    );
    118    this.props.dispatch({ type: at.TOP_SITES_CANCEL_EDIT });
    119  }
    120 
    121  onSearchShortcutsFormClose() {
    122    this.props.dispatch(
    123      ac.UserEvent({
    124        source: TOP_SITES_SOURCE,
    125        event: "SEARCH_EDIT_CLOSE",
    126      })
    127    );
    128    this.props.dispatch({ type: at.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL });
    129  }
    130 
    131  render() {
    132    const { props } = this;
    133    const { editForm, showSearchShortcutsForm } = props.TopSites;
    134    const extraMenuOptions = ["AddTopSite"];
    135    let visibleTopSites;
    136    const colors = props.Prefs.values["newNewtabExperience.colors"];
    137 
    138    // do not run this function when for startup cache
    139    if (!props.App.isForStartupCache.TopSites) {
    140      visibleTopSites = this._getVisibleTopSites()?.length;
    141    }
    142 
    143    if (props.Prefs.values["improvesearch.topSiteSearchShortcuts"]) {
    144      extraMenuOptions.push("AddSearchShortcut");
    145    }
    146 
    147    return (
    148      <ComponentPerfTimer
    149        id="topsites"
    150        initialized={props.TopSites.initialized}
    151        dispatch={props.dispatch}
    152      >
    153        <CollapsibleSection
    154          className="top-sites"
    155          id="topsites"
    156          title={props.title || { id: "newtab-section-header-topsites" }}
    157          hideTitle={true}
    158          extraMenuOptions={extraMenuOptions}
    159          showPrefName="feeds.topsites"
    160          eventSource={TOP_SITES_SOURCE}
    161          collapsed={false}
    162          isFixed={props.isFixed}
    163          isFirst={props.isFirst}
    164          isLast={props.isLast}
    165          dispatch={props.dispatch}
    166        >
    167          <TopSiteList
    168            TopSites={props.TopSites}
    169            TopSitesRows={props.TopSitesRows}
    170            dispatch={props.dispatch}
    171            topSiteIconType={topSiteIconType}
    172            colors={colors}
    173            visibleTopSites={visibleTopSites}
    174          />
    175          <div className="edit-topsites-wrapper">
    176            {editForm && (
    177              <div className="edit-topsites">
    178                <ModalOverlayWrapper
    179                  unstyled={true}
    180                  onClose={this.onEditFormClose}
    181                  innerClassName="modal"
    182                >
    183                  <TopSiteForm
    184                    site={props.TopSites.rows[editForm.index]}
    185                    onClose={this.onEditFormClose}
    186                    dispatch={this.props.dispatch}
    187                    {...editForm}
    188                  />
    189                </ModalOverlayWrapper>
    190              </div>
    191            )}
    192            {showSearchShortcutsForm && (
    193              <div className="edit-search-shortcuts">
    194                <ModalOverlayWrapper
    195                  unstyled={true}
    196                  onClose={this.onSearchShortcutsFormClose}
    197                  innerClassName="modal"
    198                >
    199                  <SearchShortcutsForm
    200                    TopSites={props.TopSites}
    201                    onClose={this.onSearchShortcutsFormClose}
    202                    dispatch={this.props.dispatch}
    203                  />
    204                </ModalOverlayWrapper>
    205              </div>
    206            )}
    207          </div>
    208        </CollapsibleSection>
    209      </ComponentPerfTimer>
    210    );
    211  }
    212 }
    213 
    214 export const TopSites = connect(state => ({
    215  App: state.App,
    216  TopSites: state.TopSites,
    217  Prefs: state.Prefs,
    218  TopSitesRows: state.Prefs.values.topSitesRows,
    219 }))(_TopSites);