Skip to content

Commit 250f95f

Browse files
authored
ntp: support configurable burn animation (#1789)
* ntp: support configurable burn animation * configurable by URL
1 parent 1c7c263 commit 250f95f

File tree

9 files changed

+53
-27
lines changed

9 files changed

+53
-27
lines changed

special-pages/pages/new-tab/app/activity/components/Activity.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,10 @@ export function ActivityConfigured({ children }) {
261261
* <ActivityConsumer />
262262
* </ActivityProvider>
263263
* ```
264+
* @param {object} props
265+
* @param {boolean} props.showBurnAnimation
264266
*/
265-
export function ActivityConsumer() {
267+
export function ActivityConsumer({ showBurnAnimation }) {
266268
const { state } = useContext(ActivityContext);
267269
const service = useContext(ActivityServiceContext);
268270
const platformName = usePlatformName();
@@ -279,7 +281,7 @@ export function ActivityConsumer() {
279281
}
280282
return (
281283
<SignalStateProvider>
282-
<BurnProvider service={service}>
284+
<BurnProvider service={service} showBurnAnimation={showBurnAnimation}>
283285
<ActivityConfigured>
284286
<ActivityBody canBurn={true} visibility={visibility} />
285287
</ActivityConfigured>

special-pages/pages/new-tab/app/activity/components/ActivityItemAnimationWrapper.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext, useLayoutEffect, useRef } from 'preact/hooks';
1+
import { useContext, useEffect, useLayoutEffect, useRef } from 'preact/hooks';
22
import { ActivityBurningSignalContext } from '../../burning/BurnProvider.js';
33
import { useComputed } from '@preact/signals';
44
import cn from 'classnames';
@@ -20,7 +20,7 @@ const BurnAnimationLazy = lazy(() => import('../../burning/BurnAnimationLottieWe
2020
*/
2121
export function ActivityItemAnimationWrapper({ children, url }) {
2222
const ref = useRef(/** @type {HTMLDivElement|null} */ (null));
23-
const { exiting, burning } = useContext(ActivityBurningSignalContext);
23+
const { exiting, burning, showBurnAnimation, doneBurning } = useContext(ActivityBurningSignalContext);
2424
const isBurning = useComputed(() => burning.value.some((x) => x === url));
2525
const isExiting = useComputed(() => exiting.value.some((x) => x === url));
2626

@@ -67,11 +67,17 @@ export function ActivityItemAnimationWrapper({ children, url }) {
6767
return (
6868
<div class={cn(styles.anim, isBurning.value && styles.burning)} ref={ref}>
6969
{!isExiting.value && children}
70-
{!isExiting.value && isBurning.value && (
70+
{!isExiting.value && isBurning.value && showBurnAnimation && (
7171
<Suspense fallback={null}>
72-
<BurnAnimationLazy url={url} />
72+
<BurnAnimationLazy url={url} doneBurning={doneBurning} />
7373
</Suspense>
7474
)}
75+
{!isExiting.value && isBurning.value && !showBurnAnimation && <NullBurner url={url} doneBurning={doneBurning} />}
7576
</div>
7677
);
7778
}
79+
80+
function NullBurner({ url, doneBurning }) {
81+
useEffect(() => doneBurning(url), [url]);
82+
return null;
83+
}

special-pages/pages/new-tab/app/burning/BurnAnimationLottieWeb.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ import { ActivityBurningSignalContext } from './BurnProvider.js';
88
*
99
* @param {Object} props The properties object.
1010
* @param {string} props.url The URL associated with the animation, used to identify or provide additional context in the dispatched events.
11+
* @param {(url: string) => void} props.doneBurning
1112
*/
12-
export function BurnAnimation({ url }) {
13+
export function BurnAnimation({ url, doneBurning }) {
1314
const ref = useRef(/** @type {Lottie} */ null);
1415
const json = useContext(ActivityBurningSignalContext);
1516
useEffect(() => {
1617
if (!ref.current) return;
1718
let finished = false;
1819
let timer = null;
1920

20-
const publish = (reason) => {
21+
const publish = (_reason) => {
2122
if (finished) return;
22-
window.dispatchEvent(new CustomEvent('done-burning', { detail: { url, reason } }));
23+
doneBurning(url);
2324
finished = true;
2425
clearTimeout(timer);
2526
};
@@ -52,6 +53,6 @@ export function BurnAnimation({ url }) {
5253
publish('unmount occurred');
5354
}
5455
};
55-
}, [url, json]);
56+
}, [url, json, doneBurning]);
5657
return <div ref={ref} data-lottie-player></div>;
5758
}

special-pages/pages/new-tab/app/burning/BurnProvider.js

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { h, createContext } from 'preact';
2-
import { useContext, useEffect } from 'preact/hooks';
2+
import { useCallback, useContext } from 'preact/hooks';
33
import { batch, signal, useSignal, useSignalEffect } from '@preact/signals';
44
import { useEnv } from '../../../../shared/components/EnvironmentProvider.js';
55
import { ActivityInteractionsContext } from './ActivityInteractionsContext.js';
@@ -13,15 +13,20 @@ export const ActivityBurningSignalContext = createContext({
1313
exiting: signal([]),
1414
/** @type {import("@preact/signals").Signal<{state: 'loading' | 'ready' | 'error', data: null | Record<string, any>}>} */
1515
animation: signal({ state: 'loading', data: null }),
16+
/** @type {boolean} */
17+
showBurnAnimation: true,
18+
/** @type {(url: string) => void} */
19+
doneBurning: (_url) => {},
1620
});
1721

1822
/**
1923
* @param {object} props
2024
* @param {import("preact").ComponentChild} props.children
2125
* @param {{confirmBurn: (url: string) => Promise<{action: 'burn' | 'none'}>; disableBroadcast: () => void; enableBroadcast: () => void }} props.service
26+
* @param {boolean} [props.showBurnAnimation] - defaults to true to match original implementation
2227
*
2328
*/
24-
export function BurnProvider({ children, service }) {
29+
export function BurnProvider({ children, service, showBurnAnimation = true }) {
2530
const burning = useSignal(/** @type {string[]} */ ([]));
2631
const exiting = useSignal(/** @type {string[]} */ ([]));
2732
const animation = useSignal({ state: /** @type {'loading' | 'ready' | 'error'} */ ('loading'), data: null });
@@ -94,23 +99,21 @@ export function BurnProvider({ children, service }) {
9499
};
95100
});
96101

97-
useEffect(() => {
98-
const handler = (e) => {
99-
if (e.detail.url) {
102+
/** @type {(url: string) => void} */
103+
const doneBurning = useCallback(
104+
(url) => {
105+
if (url) {
100106
batch(() => {
101-
burning.value = burning.value.filter((x) => x !== e.detail.url);
102-
exiting.value = exiting.value.concat(e.detail.url);
107+
burning.value = burning.value.filter((x) => x !== url);
108+
exiting.value = exiting.value.concat(url);
103109
});
104110
}
105-
};
106-
window.addEventListener('done-burning', handler);
107-
return () => {
108-
window.removeEventListener('done-burning', handler);
109-
};
110-
}, [burning, exiting]);
111+
},
112+
[burning, exiting],
113+
);
111114

112115
return (
113-
<ActivityBurningSignalContext.Provider value={{ burning, exiting, animation }}>
116+
<ActivityBurningSignalContext.Provider value={{ burning, exiting, animation, showBurnAnimation, doneBurning }}>
114117
<ActivityInteractionsContext.Provider value={{ didClick }}>{children}</ActivityInteractionsContext.Provider>
115118
</ActivityBurningSignalContext.Provider>
116119
);

special-pages/pages/new-tab/app/protections/components/ProtectionsConsumer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function ProtectionsReadyState({ data, config }) {
5050
>
5151
{config.feed === 'activity' && (
5252
<ActivityProvider>
53-
<ActivityConsumer />
53+
<ActivityConsumer showBurnAnimation={config.showBurnAnimation ?? true} />
5454
</ActivityProvider>
5555
)}
5656
{config.feed === 'privacy-stats' && (

special-pages/pages/new-tab/app/protections/mocks/protections.mock-transport.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export function protectionsMockTransport() {
8989
config.feed = 'activity';
9090
}
9191

92+
if (url.searchParams.get('protections.burn') === 'false') {
93+
config.showBurnAnimation = false;
94+
}
95+
9296
return Promise.resolve(config);
9397
}
9498
default: {

special-pages/pages/new-tab/app/protections/protections.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ title: Protections Report
4040
```json
4141
{
4242
"expansion": "collapsed",
43-
"feed": "privacy-stats"
43+
"feed": "privacy-stats",
44+
"showBurnAnimation": true
4445
}
4546
```
4647

@@ -63,6 +64,7 @@ title: Protections Report
6364
```json
6465
{
6566
"expansion": "collapsed",
66-
"feed": "privacy-stats"
67+
"feed": "privacy-stats",
68+
"showBurnAnimation": true
6769
}
6870
```

special-pages/pages/new-tab/messages/types/protections-config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
"privacy-stats",
1717
"activity"
1818
]
19+
},
20+
"showBurnAnimation": {
21+
"description": "Boolean flag to explicitly enable or disable the burn animations",
22+
"type": "boolean"
1923
}
2024
}
2125
}

special-pages/pages/new-tab/types/new-tab.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ export interface ProtectionsSetConfigNotification {
556556
export interface ProtectionsConfig {
557557
expansion: Expansion;
558558
feed: FeedType;
559+
/**
560+
* Boolean flag to explicitly enable or disable the burn animations
561+
*/
562+
showBurnAnimation?: boolean;
559563
}
560564
/**
561565
* Generated from @see "../messages/reportInitException.notify.json"

0 commit comments

Comments
 (0)