Skip to content

Commit

Permalink
ntp: more SR feedback (#1449)
Browse files Browse the repository at this point in the history
* ntp: more SR feedback

* simpler company icon
  • Loading branch information
shakyShane authored and mgurgel committed Feb 3, 2025
1 parent 2b67faa commit 879654a
Show file tree
Hide file tree
Showing 15 changed files with 328 additions and 224 deletions.
1 change: 0 additions & 1 deletion special-pages/pages/history/app/icons/Cross.svg

This file was deleted.

2 changes: 2 additions & 0 deletions special-pages/pages/new-tab/app/components/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ body {
padding-bottom: var(--sp-16);
margin-left: auto;
margin-right: auto;
/* prevent the scrollbar affecting the width */
padding-left: calc(100vw - 100%);
}

body:has([data-reset-layout="true"]) .tube {
Expand Down
216 changes: 166 additions & 50 deletions special-pages/pages/new-tab/app/components/CompanyIcon.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,44 @@
import styles from './CompanyIcon.module.css';
import { DDG_STATS_OTHER_COMPANY_IDENTIFIER } from '../privacy-stats/constants.js';
import { h } from 'preact';
import { useState } from 'preact/hooks';
import { memo } from 'preact/compat';

const mappings = {
'google-analytics-google': 'google-analytics',
'google-ads-google': 'google-ads',
};

const states = /** @type {const} */ ({
loading: 'loading',
loaded: 'loaded',
loadingFallback: 'loadingFallback',
loadedFallback: 'loadedFallback',
errored: 'errored',
});
export const CompanyIcon = memo(
/**
* @param {object} props
* @param {string} props.displayName
*/
function CompanyIcon({ displayName }) {
const icon = displayName.toLowerCase().split('.')[0];
const cleaned = icon.replace(/[^a-z ]/g, '').replace(/ /g, '-');
const id = cleaned in mappings ? mappings[cleaned] : cleaned;
const firstChar = id[0];

/**
* @typedef {states[keyof states]} State
*/
if (icon === DDG_STATS_OTHER_COMPANY_IDENTIFIER) {
return (
<span className={styles.icon}>
<Other />
</span>
);
}

/**
* @param {object} props
* @param {string} props.displayName
*/
export function CompanyIcon({ displayName }) {
const icon = displayName.toLowerCase().split('.')[0];
const cleaned = icon.replace(/[^a-z ]/g, '').replace(/ /g, '-');
const id = cleaned in mappings ? mappings[cleaned] : cleaned;
const firstChar = id[0];
const [state, setState] = useState(/** @type {State} */ (states.loading));

const src =
state === 'loading' || state === 'loaded'
// prettier-ignore
const src = names.has(id)
? `./company-icons/${id}.svg`
: state === 'loadingFallback' || state === 'loadedFallback'
? `./company-icons/${firstChar}.svg`
: null;
: `./company-icons/${firstChar}.svg`;

if (src === null || icon === DDG_STATS_OTHER_COMPANY_IDENTIFIER) {
return (
<span className={styles.icon}>
<Other />
<span className={styles.icon} title={displayName}>
<img src={src} alt={''} class={styles.companyImgIcon} data-loaded="true" />
</span>
);
}

return (
<span className={styles.icon}>
<img
src={src}
alt={''}
class={styles.companyImgIcon}
data-loaded={state === states.loaded || state === states.loadedFallback}
onLoad={() => setState((prev) => (prev === states.loading ? states.loaded : states.loadedFallback))}
onError={() => {
setState((prev) => {
if (prev === states.loading) return states.loadingFallback;
return states.errored;
});
}}
/>
</span>
);
}
},
);

function Other() {
return (
Expand All @@ -76,3 +52,143 @@ function Other() {
</svg>
);
}

/**
* A static list of names representing the icons in `./public/company-icons`
* We don't want the bundler to crawl through that folder, or include the SVGs in JS,
* so this static list have to do for now. Perhaps it can be generated later.
*
* @type {Set<string>}
*/
const names = new Set([
'33across',
'a',
'acuityads',
'adform',
'adjust',
'adobe',
'akamai',
'amazon',
'amplitude',
'appsflyer',
'automattic',
'b',
'beeswax',
'bidtellect',
'branch-metrics',
'braze',
'bugsnag',
'bytedance',
'c',
'chartbeat',
'cloudflare',
'cognitiv',
'comscore',
'crimtan-holdings',
'criteo',
'd',
'deepintent',
'e',
'exoclick',
'eyeota',
'f',
'facebook',
'g',
'google',
'google-ads',
'google-analytics',
'gumgum',
'h',
'hotjar',
'i',
'id5',
'improve-digital',
'index-exchange',
'inmar',
'instagram',
'intent-iq',
'iponweb',
'j',
'k',
'kargo',
'kochava',
'l',
'line',
'linkedin',
'liveintent',
'liveramp',
'loopme-ltd',
'lotame-solutions',
'm',
'magnite',
'mediamath',
'medianet-advertising',
'mediavine',
'merkle',
'microsoft',
'mixpanel',
'n',
'narrative',
'nativo',
'neustar',
'new-relic',
'o',
'onetrust',
'openjs-foundation',
'openx',
'opera-software',
'oracle',
'other',
'other-dark',
'outbrain',
'p',
'pinterest',
'prospect-one',
'pubmatic',
'pulsepoint',
'q',
'quantcast',
'r',
'rhythmone',
'roku',
'rtb-house',
'rubicon',
's',
'salesforce',
'semasio',
'sharethrough',
'simplifi-holdings',
'smaato',
'snap',
'sonobi',
'sovrn-holdings',
'spotx',
'supership',
'synacor',
't',
'taboola',
'tapad',
'teads',
'the-nielsen-company',
'the-trade-desk',
'triplelift',
'twitter',
'u',
'unruly-group',
'urban-airship',
'v',
'verizon-media',
'w',
'warnermedia',
'wpp',
'x',
'xaxis',
'y',
'yahoo-japan',
'yandex',
'yieldmo',
'youtube',
'z',
'zeotap',
'zeta-global',
]);
4 changes: 2 additions & 2 deletions special-pages/pages/new-tab/app/components/ImageWithState.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ export function ImageWithState({ faviconSrc, faviconMax, title, etldPlusOne, the
const chars = etldPlusOne.slice(0, 2);
return (
<div class={cn(styles.favicon, sizeClass, styles.faviconText)} style={style} data-state={state}>
<span>{chars[0]}</span>
<span>{chars[1]}</span>
<span aria-hidden={true}>{chars[0]}</span>
<span aria-hidden={true}>{chars[1]}</span>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function ShowHideButton({ text, onClick, buttonAttrs = {}, shape = 'none'
{...buttonAttrs}
class={cn(styles.button, shape === 'round' && styles.round, !!showText && styles.withText)}
aria-label={text}
data-toggle="true"
onClick={onClick}
>
{showText ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

/** todo: is this a re-usable button, yet? */
.customizeButton {
backdrop-filter: blur(48px);
background-color: var(--ntp-surface-background-color);
border: 1px solid var(--ntp-surface-border-color);
border-radius: var(--border-radius-sm);
Expand Down
36 changes: 26 additions & 10 deletions special-pages/pages/new-tab/app/favorites/components/Favorites.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { TileRow } from './TileRow.js';
import { FavoritesContext } from './FavoritesProvider.js';
import { CustomizerContext, CustomizerThemesContext } from '../../customizer/CustomizerProvider.js';
import { signal, useComputed } from '@preact/signals';
import { eventToTarget, useDocumentVisibility } from '../../utils.js';
import { eventToTarget, useOnMiddleClick } from '../../utils.js';
import { useDocumentVisibility } from '../../../../../shared/components/DocumentVisibility';

/**
* @typedef {import('../../../types/new-tab.js').Expansion} Expansion
Expand Down Expand Up @@ -125,6 +126,7 @@ export function Favorites({ gridRef, favorites, expansion, toggle, openContextMe
*/
function VirtualizedGridRows({ WIDGET_ID, rowHeight, favorites, expansion, openFavorite, openContextMenu, add }) {
const platformName = usePlatformName();
const visibility = useDocumentVisibility();

// convert the list of favorites into chunks of length ROW_CAPACITY
const rows = useMemo(() => {
Expand Down Expand Up @@ -153,17 +155,20 @@ function VirtualizedGridRows({ WIDGET_ID, rowHeight, favorites, expansion, openF
? rowHeight
: rows.length * rowHeight;

const clickHandler = getOnClickHandler(openFavorite, platformName);
useOnMiddleClick(safeAreaRef, clickHandler);

return (
<div
className={styles.grid}
style={{ height: containerHeight + 'px' }}
id={WIDGET_ID}
ref={safeAreaRef}
onContextMenu={getContextMenuHandler(openContextMenu)}
onClick={getOnClickHandler(openFavorite, platformName)}
onClick={clickHandler}
>
{rows.length === 0 && <TileRow key={'empty-rows'} items={[]} topOffset={0} add={add} visibility={'visible'} />}
{rows.length > 0 && <Inner rows={rows} safeAreaRef={safeAreaRef} rowHeight={rowHeight} add={add} />}
{rows.length > 0 && <Inner rows={rows} safeAreaRef={safeAreaRef} rowHeight={rowHeight} add={add} visibility={visibility} />}
</div>
);
}
Expand All @@ -178,13 +183,13 @@ function VirtualizedGridRows({ WIDGET_ID, rowHeight, favorites, expansion, openF
* @param {Favorite[][]} props.rows
* @param {import("preact").RefObject<HTMLDivElement>} props.safeAreaRef
* @param {number} props.rowHeight
* @param {DocumentVisibilityState} props.visibility
* @param {()=>void} props.add
*/
function Inner({ rows, safeAreaRef, rowHeight, add }) {
function Inner({ rows, safeAreaRef, rowHeight, add, visibility }) {
const { onConfigChanged, state } = useContext(FavoritesContext);
const [expansion, setExpansion] = useState(state.config?.expansion || 'collapsed');
const documentVisibility = useDocumentVisibility();
const { start, end } = useVisibleRows(rows, rowHeight, safeAreaRef);
const { start, end } = useVisibleRows(rows, rowHeight, safeAreaRef, expansion);

// force the children to be rendered after the main thread is cleared
useEffect(() => {
Expand Down Expand Up @@ -214,7 +219,7 @@ function Inner({ rows, safeAreaRef, rowHeight, add }) {
{subsetOfRowsToRender.map((items, rowIndex) => {
const topOffset = expansion === 'expanded' ? (start + rowIndex) * rowHeight : 0;
const keyed = `-${start + rowIndex}-`;
return <TileRow key={keyed} items={items} topOffset={topOffset} add={add} visibility={documentVisibility} />;
return <TileRow key={keyed} items={items} topOffset={topOffset} add={add} visibility={visibility} />;
})}
</Fragment>
);
Expand All @@ -228,9 +233,10 @@ function Inner({ rows, safeAreaRef, rowHeight, add }) {
* @param {Array} rows - The array of rows to be virtually rendered. Each row represents an item in the list.
* @param {number} rowHeight - The fixed height of each row in pixels.
* @param {Object} safeAreaRef - A React ref object pointing to the DOM element that serves as the virtualized list’s container.
* @param {Expansion} expansion - A React ref object pointing to the DOM element that serves as the virtualized list’s container.
* @return {Object} An object containing the calculated `start` and `end` indices of the visible rows.
*/
function useVisibleRows(rows, rowHeight, safeAreaRef) {
function useVisibleRows(rows, rowHeight, safeAreaRef, expansion) {
// set the start/end indexes of the elements
const [{ start, end }, setVisibleRange] = useState({ start: 0, end: 1 });

Expand Down Expand Up @@ -265,10 +271,20 @@ function useVisibleRows(rows, rowHeight, safeAreaRef) {
}
const startIndex = Math.floor(start / rowHeight);
const endIndex = Math.min(Math.ceil(end / rowHeight), rows.length);
setVisibleRange({ start: startIndex, end: endIndex });

// don't set state if the offset didn't change
setVisibleRange((prev) => {
if (startIndex !== prev.start || endIndex !== prev.end) {
return { start: startIndex, end: endIndex };
}
return prev;
});
}

useLayoutEffect(() => {
// ignore scrolling events if collapsed
if (expansion === 'collapsed') return;

mainScrollerRef.current = document.querySelector('[data-main-scroller]') || document.documentElement;
contentTubeRef.current = document.querySelector('[data-content-tube]') || document.body;
if (!contentTubeRef.current || !mainScrollerRef.current) console.warn('missing elements');
Expand All @@ -287,7 +303,7 @@ function useVisibleRows(rows, rowHeight, safeAreaRef) {
return () => {
controller.abort();
};
}, [rows.length]);
}, [rows.length, expansion]);

useEffect(() => {
let lastWindowHeight = window.innerHeight;
Expand Down
Loading

0 comments on commit 879654a

Please sign in to comment.