Skip to content

Commit

Permalink
animation stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
shakyShane committed Jan 26, 2025
1 parent d75d7d3 commit b1b9ee6
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 49 deletions.
20 changes: 12 additions & 8 deletions special-pages/pages/new-tab/app/activity/BurnProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useContext, useEffect } from 'preact/hooks';
import { ActivityApiContext, ActivityServiceContext } from './ActivityProvider';
import { ACTION_BURN } from './constants.js';
import { useEnv } from '../../../../shared/components/EnvironmentProvider.js';
import { signal, useSignal } from '@preact/signals';
import { batch, signal, useSignal, useSignalEffect } from '@preact/signals';

export const ActivityBurningSignalContext = createContext({
/** @type {import("@preact/signals").Signal<string[]>} */
Expand Down Expand Up @@ -38,24 +38,28 @@ export function BurnProvider({ children }) {
}
}

useEffect(() => {
useSignalEffect(() => {
const handler = (e) => {
if (e.detail.url) {
burning.value = burning.value.filter((x) => x !== e.detail.url);
exiting.value = exiting.value.concat(e.detail.url);
batch(() => {
burning.value = burning.value.filter((x) => x !== e.detail.url);
exiting.value = exiting.value.concat(e.detail.url);
});
}
};
window.addEventListener('done-burning', handler);
return () => {
window.removeEventListener('done-burning', handler);
};
}, [service]);
});

useEffect(() => {
const handler = (e) => {
if (e.detail.url) {
service?.burn(e.detail.url);
}
if (!service) return console.warn('could not access the service');
if (!e.detail.url) return console.warn('missing detail.url on the custom event');

exiting.value = exiting.value.filter((x) => x !== e.detail.url);
service.burn(e.detail.url);
};
window.addEventListener('done-exiting', handler);
return () => {
Expand Down
50 changes: 42 additions & 8 deletions special-pages/pages/new-tab/app/activity/components/Activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { useComputed } from '@preact/signals';
*/
function ActivityConfigured({ parentRef, expansion, toggle }) {
const platformName = usePlatformName();
const { isReducedMotion } = useEnv();
const expanded = expansion === 'expanded';
const { activity } = useContext(SignalStateContext);
const count = useComputed(() => {
Expand All @@ -52,7 +51,7 @@ function ActivityConfigured({ parentRef, expansion, toggle }) {
// see: https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/
const WIDGET_ID = useId();
const TOGGLE_ID = useId();
const canBurn = platformName === 'macos' && !isReducedMotion;
const canBurn = platformName === 'macos';

return (
<div class={styles.root} ref={parentRef}>
Expand All @@ -78,25 +77,26 @@ function ActivityConfigured({ parentRef, expansion, toggle }) {
function ActivityBody({ canBurn }) {
const { didClick } = useContext(ActivityApiContext);
const documentVisibility = useDocumentVisibility();
const { isReducedMotion } = useEnv();
const { keys } = useContext(SignalStateContext);

return (
<ul class={styles.activity} onClick={didClick}>
{keys.value.map((id, index) => {
return <Item id={id} key={id} canBurn={canBurn} documentVisibility={documentVisibility} />;
if (canBurn && !isReducedMotion) return <BurnableItem id={id} key={id} documentVisibility={documentVisibility} />;
return <RemovableItem id={id} key={id} canBurn={canBurn} documentVisibility={documentVisibility} />;
})}
</ul>
);
}

const Item = memo(Item_);
const BurnableItem = memo(BurnableItem_);
/**
* @param {object} props
* @param {string} props.id
* @param {boolean} props.canBurn
* @param {"visible" | "hidden"} props.documentVisibility
*/
function Item_({ id, canBurn, documentVisibility }) {
function BurnableItem_({ id, documentVisibility }) {
const { activity } = useContext(SignalStateContext);
const item = useComputed(() => activity.value.items[id]);
return (
Expand All @@ -107,7 +107,7 @@ function Item_({ id, canBurn, documentVisibility }) {
favoriteSrc={item.value.favoriteSrc}
faviconMax={item.value.faviconMax}
etldPlusOne={item.value.etldPlusOne}
canBurn={canBurn}
canBurn={true}
documentVisibility={documentVisibility}
>
<TrackerStatus id={id} />
Expand All @@ -117,6 +117,32 @@ function Item_({ id, canBurn, documentVisibility }) {
);
}

const RemovableItem = memo(RemovableItem_);
/**
* @param {object} props
* @param {string} props.id
* @param {boolean} props.canBurn
* @param {"visible" | "hidden"} props.documentVisibility
*/
function RemovableItem_({ id, canBurn, documentVisibility }) {
const { activity } = useContext(SignalStateContext);
const item = useComputed(() => activity.value.items[id]);
return (
<ActivityItem
title={item.value.title}
url={id}
favoriteSrc={item.value.favoriteSrc}
faviconMax={item.value.faviconMax}
etldPlusOne={item.value.etldPlusOne}
canBurn={canBurn}
documentVisibility={documentVisibility}
>
<TrackerStatus id={id} />
<HistoryItems id={id} />
</ActivityItem>
);
}

const DDG_MAX_TRACKER_ICONS = 3;
/**
* @param {object} props
Expand All @@ -127,10 +153,18 @@ function TrackerStatus({ id }) {
const { activity } = useContext(SignalStateContext);
const status = useComputed(() => activity.value.trackingStatus[id]);
const other = status.value.trackerCompanies.length - DDG_MAX_TRACKER_ICONS;
const { env } = useEnv();

if (env === 'development') {
console.groupCollapsed(`trackingStatus ${id}`);
console.log(' [total]', status.value.totalCount);
console.log('[companies]', status.value.trackerCompanies);
console.groupEnd();
}

const companyIconsMax = other === 0 ? DDG_MAX_TRACKER_ICONS : DDG_MAX_TRACKER_ICONS - 1;
const icons = status.value.trackerCompanies.slice(0, companyIconsMax).map((item, index) => {
return <CompanyIcon displayName={item.displayName} key={index} />;
return <CompanyIcon displayName={item.displayName} key={item} />;
});

const otherIcon = other > 0 ? <span class={styles.otherIcon}>+{other + 1}</span> : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,22 @@ export function AnimWrapper({ children, url }) {
if (isBurning.value && ref.current) {
const element = ref.current;
element.style.height = element.scrollHeight + 'px';
}
if (isExiting.value && ref.current) {
} else if (isExiting.value && ref.current) {
const element = ref.current;
let anim = element.animate([{ height: element.style.height }, { height: '0px' }], {
const anim = element.animate([{ height: element.style.height }, { height: '0px' }], {
duration: 200,
iterations: 1,
fill: 'both',
easing: 'ease-in-out',
});
const handler = () => {
const handler = (e) => {
if (canceled) return;
if (sent) return;
sent = true;
window.dispatchEvent(new CustomEvent('done-exiting', { detail: { url } }));
anim.removeEventListener('finish', handler);
window.dispatchEvent(new CustomEvent('done-exiting', { detail: { url, reason: 'animation completed' } }));
};
anim.addEventListener('finish', handler);
anim.addEventListener('finish', handler, { once: true });
document.addEventListener('visibilitychange', handler, { once: true });
return () => {
anim.removeEventListener('finish', handler);
Expand All @@ -135,7 +135,7 @@ export function AnimWrapper({ children, url }) {
return () => {
canceled = true;
};
}, [isBurning.value, isExiting.value]);
}, [isBurning.value, isExiting.value, url]);

return (
<div class={cn(styles.anim, isBurning.value && styles.burning)} ref={ref}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export function BurnAnimation({ url }) {
const curr = /** @type {import("@lottielab/lottie-player/web").default} */ (ref.current);
let finished = false;
const int = setTimeout(() => {
window.dispatchEvent(new CustomEvent('done-burning', { detail: { url } }));
window.dispatchEvent(new CustomEvent('done-burning', { detail: { url, reason: 'timeout occurred' } }));
curr.stop();
finished = true;
}, 1200);
return () => {
clearTimeout(int);
if (!finished) {
window.dispatchEvent(new CustomEvent('done-burning', { detail: { url } }));
window.dispatchEvent(new CustomEvent('done-burning', { detail: { url, reason: 'unmount occurred' } }));
}
};
}, [url]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export class ActivityPage {
});
}
async burnsItem() {
// const { page } = this;
// await page.pause();
await this.context().getByRole('button', { name: 'Clear browsing history and data for example.com' }).click();
const result = await this.ntp.mocks.waitForCallCount({ method: 'activity_burn', count: 1 });
expect(result[0].payload).toStrictEqual({
Expand Down
15 changes: 15 additions & 0 deletions special-pages/pages/new-tab/app/activity/mocks/activity.mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ export const activityMocks = {
url: 'https://example.com/bathrooms/toilets',
relativeTime: 'Just now',
},
{
title: '/kitchen/sinks',
url: 'https://example.com/kitchen/sinks',
relativeTime: '50 mins ago',
},
{
title: '/gardening/tools',
url: 'https://example.com/gardening/tools',
relativeTime: '18 hrs ago',
},
{
title: '/lighting/fixtures',
url: 'https://example.com/lighting/fixtures',
relativeTime: '1 day ago',
},
],
},
{
Expand Down
72 changes: 48 additions & 24 deletions special-pages/pages/new-tab/app/components/CompanyIcon.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
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';

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

const states = /** @type {const} */ ({
loading: 'loading',
loaded: 'loaded',
loadingFallback: 'loadingFallback',
loadedFallback: 'loadedFallback',
errored: 'errored',
});

/**
* @typedef {states[keyof states]} State
*/

/**
* @param {object} props
Expand All @@ -9,33 +26,40 @@ import { h } from 'preact';
export function CompanyIcon({ displayName }) {
const icon = displayName.toLowerCase().split('.')[0];
const cleaned = icon.replace(/[^a-z ]/g, '').replace(/ /g, '-');
const firstChar = cleaned[0];
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'
? `./company-icons/${id}.svg`
: state === 'loadingFallback' || state === 'loadedFallback'
? `./company-icons/${firstChar}.svg`
: null;

if (src === null || icon === DDG_STATS_OTHER_COMPANY_IDENTIFIER) {
return (
<span className={styles.icon}>
<Other />
</span>
);
}

return (
<span className={styles.icon}>
{icon === DDG_STATS_OTHER_COMPANY_IDENTIFIER && <Other />}
{icon !== DDG_STATS_OTHER_COMPANY_IDENTIFIER && (
<img
src={`./company-icons/${cleaned}.svg`}
alt={icon + ' icon'}
className={styles.companyImgIcon}
onLoad={(e) => {
if (!e.target) return;
if (!(e.target instanceof HTMLImageElement)) return;
e.target.dataset.loaded = String(true);
}}
onError={(e) => {
if (!e.target) return;
if (!(e.target instanceof HTMLImageElement)) return;
if (e.target.dataset.loadingFallback) {
e.target.dataset.errored = String(true);
return;
}
e.target.dataset.loadingFallback = String(true);
e.target.src = `./company-icons/${firstChar}.svg`;
}}
/>
)}
<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>
);
}
Expand Down

0 comments on commit b1b9ee6

Please sign in to comment.