Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ntp: more SR feedback #1449

Merged
merged 2 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
]);
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
Loading