Skip to content
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
29 changes: 29 additions & 0 deletions special-pages/pages/new-tab/app/InlineError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { h } from 'preact';
import { ErrorBoundary } from '../../../shared/components/ErrorBoundary.js';
import { useMessaging } from './types.js';

/**
* @param {object} props
* @param {import("preact").ComponentChild} props.children
* @param {string} props.named
* @param {(message: string) => import("preact").ComponentChild} [props.fallback]
*/
export function InlineError({ children, named, fallback }) {
const messaging = useMessaging();
/**
* @param {any} error
* @param {string} id
*/
const didCatch = (error, id) => {
const message = error?.message || error?.error || 'unknown';
const composed = `Customizer section '${id}' threw an exception: ` + message;
messaging.reportPageException({ message: composed });
};
const inlineMessage = 'A problem occurred with this feature. DuckDuckGo was notified';
const fallbackElement = fallback?.(inlineMessage) || <p>{inlineMessage}</p>;
return (
<ErrorBoundary didCatch={(error) => didCatch(error, named)} fallback={fallbackElement}>
{children}
</ErrorBoundary>
);
}
4 changes: 1 addition & 3 deletions special-pages/pages/new-tab/app/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,7 @@ export function App() {
data-browser-panel
>
<div class={styles.asideContent}>
<div class={styles.asideContentInner}>
<CustomizerDrawer displayChildren={displayChildren} />
</div>
<CustomizerDrawer displayChildren={displayChildren} />
</div>
</aside>
)}
Expand Down
10 changes: 5 additions & 5 deletions special-pages/pages/new-tab/app/components/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,18 @@ body:has([data-reset-layout="true"]) .tube {
.layout[data-animating="true"] & {
overflow: hidden;
}

.layout[data-animating="false"] &[aria-hidden=true] {
visibility: hidden;
opacity: 0;
}
}

.asideContent {
opacity: 1;
width: var(--ntp-drawer-width);
}

.asideContentInner {
padding: 1rem;
padding-right: calc(1rem - var(--ntp-drawer-scroll-width));
}

.asideScroller {
&::-webkit-scrollbar {
width: var(--ntp-drawer-scroll-width);
Expand Down
81 changes: 54 additions & 27 deletions special-pages/pages/new-tab/app/components/BackgroundProvider.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Fragment, h } from 'preact';
import cn from 'classnames';
import styles from './BackgroundReceiver.module.css';
import { values } from '../customizer/values.js';
import { useContext } from 'preact/hooks';
import { useContext, useState } from 'preact/hooks';
import { CustomizerContext } from '../customizer/CustomizerProvider.js';
import { detectThemeFromHex } from '../customizer/utils.js';

Expand Down Expand Up @@ -92,20 +93,9 @@ export function BackgroundConsumer({ browser }) {
const gradient = values.gradients[background.value];
return (
<Fragment key="gradient">
<ImageCrossFade src={gradient.path}></ImageCrossFade>
<div
class={styles.root}
data-animate="false"
data-testid="BackgroundConsumer"
style={{
backgroundColor: gradient.fallback,
backgroundImage: `url(${gradient.path})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
}}
/>
<div
class={styles.root}
data-animate="false"
className={styles.root}
style={{
backgroundImage: `url(gradients/grain.png)`,
backgroundRepeat: 'repeat',
Expand All @@ -118,23 +108,60 @@ export function BackgroundConsumer({ browser }) {
}
case 'userImage': {
const img = background.value;
return (
<div
class={styles.root}
data-animate="true"
data-testid="BackgroundConsumer"
style={{
backgroundImage: `url(${img.src})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center center',
}}
></div>
);
return <ImageCrossFade src={img.src} />;
}
default: {
console.warn('Unreachable!');
return <div className={styles.root}></div>;
}
}
}

/**
* @param {object} props
* @param {string} props.src
*/
function ImageCrossFade({ src }) {
/**
* Proxy the image source, so that we can keep the old
* image around whilst the new one is loading.
*/
const [stable, setStable] = useState(src);
/**
* Trigger the animation:
*
* NOTE: this animation is deliberately NOT done purely with CSS-triggered state.
* Whilst debugging in WebKit, I found the technique below to be 100% reliable
* in terms of fading a new image over the top of an existing one.
*
* If you find a better way, please test in webkit-based browsers
*/
return (
<Fragment>
<img src={stable} class={styles.root} style={{ display: src === stable ? 'none' : 'block' }} />
<img
src={src}
class={cn(styles.root, styles.over)}
onLoad={(e) => {
const elem = /** @type {HTMLImageElement} */ (e.target);

// HACK: This is what I needed to force, to get 100% predictability. 🤷
elem.style.opacity = '0';

const anim = elem.animate([{ opacity: '0' }, { opacity: '1' }], {
duration: 250,
iterations: 1,
easing: 'ease-in-out',
fill: 'both',
});

// when the fade completes, we want to reset the stable `src`.
// This allows the image underneath to be updated but also allows us to un-mount the fader on top.
anim.onfinish = () => {
setStable(src);
};
}}
/>
</Fragment>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@
inset: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
pointer-events: none;

&[data-animate="true"] {
transition: all .3s ease-in-out;
transition: background .25s ease-in-out;
}

&[data-background-kind="default"][data-theme=dark] {
background: var(--default-dark-bg);
}
&[data-background-kind="default"][data-theme=light] {
background: var(--default-light-bg);
}
}

.under {
opacity: 1;
}
.over {
opacity: 0;
}
25 changes: 14 additions & 11 deletions special-pages/pages/new-tab/app/components/Components.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mainExamples, otherExamples } from './Examples.jsx';
import { useThemes } from '../customizer/themes.js';
import { useSignal } from '@preact/signals';
import { BackgroundConsumer } from './BackgroundProvider.js';
import { CustomizerThemesContext } from '../customizer/CustomizerProvider.js';
const url = new URL(window.location.href);

const list = {
Expand Down Expand Up @@ -32,18 +33,20 @@ export function Components() {
const { main, browser } = useThemes(dataSignal);

return (
<div class={styles.main} data-main-scroller data-theme={main}>
<BackgroundConsumer browser={browser} />
<div data-content-tube class={styles.contentTube}>
{isolated && <Isolated entries={filtered} e2e={e2e} />}
{!isolated && (
<Fragment>
<DebugBar id={ids[0]} ids={ids} entries={entries} />
<Stage entries={/** @type {any} */ (filtered)} />
</Fragment>
)}
<CustomizerThemesContext.Provider value={{ main, browser }}>
<div class={styles.main} data-main-scroller data-theme={main}>
<BackgroundConsumer browser={browser} />
<div data-content-tube class={styles.contentTube}>
{isolated && <Isolated entries={filtered} e2e={e2e} />}
{!isolated && (
<Fragment>
<DebugBar id={ids[0]} ids={ids} entries={entries} />
<Stage entries={/** @type {any} */ (filtered)} />
</Fragment>
)}
</div>
</div>
</div>
</CustomizerThemesContext.Provider>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import styles from './DismissButton.module.css';
* @param {object} props
* @param {string} [props.className]
* @param {() => void} [props.onClick]
* @param {import("preact").ComponentProps<"button"> & Record<string, string>} [props.buttonProps]
*/
export function DismissButton({ className, onClick }) {
export function DismissButton({ className, onClick, buttonProps = {} }) {
const { t } = useTypedTranslation();

return (
<button class={cn(styles.btn, className)} onClick={onClick} aria-label={t('ntp_dismiss')} data-testid="dismissBtn">
<button class={cn(styles.btn, className)} onClick={onClick} aria-label={t('ntp_dismiss')} data-testid="dismissBtn" {...buttonProps}>
<Cross />
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
border-radius: 50%;
transition: all .3s;

svg {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}

&:hover {
background-color: var(--color-black-at-9);
cursor: pointer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,40 +42,33 @@ export function BackgroundSection({ data, onNav, onUpload, select }) {
}

return (
<div class={styles.section}>
<h3 class={styles.sectionTitle}>Background</h3>
<ul class={cn(styles.sectionBody, styles.bgList)} role="radiogroup">
<li class={styles.bgListItem}>
<DefaultPanel
checked={data.value.background.kind === 'default'}
onClick={() => select({ background: { kind: 'default' } })}
/>
</li>
<li class={styles.bgListItem}>
<ColorPanel
checked={data.value.background.kind === 'color' || data.value.background.kind === 'hex'}
color={displayColor}
onClick={() => onNav('color')}
/>
</li>
<li class={styles.bgListItem}>
<GradientPanel
checked={data.value.background.kind === 'gradient'}
gradient={gradient}
onClick={() => onNav('gradient')}
/>
</li>
<li class={styles.bgListItem}>
<BackgroundImagePanel
checked={data.value.background.kind === 'userImage'}
onClick={() => onNav('image')}
data={data}
upload={onUpload}
browserTheme={browser}
/>
</li>
</ul>
</div>
<ul class={cn(styles.bgList)} role="radiogroup">
<li class={styles.bgListItem}>
<DefaultPanel
checked={data.value.background.kind === 'default'}
onClick={() => select({ background: { kind: 'default' } })}
/>
</li>
<li class={styles.bgListItem}>
<ColorPanel
checked={data.value.background.kind === 'color' || data.value.background.kind === 'hex'}
color={displayColor}
onClick={() => onNav('color')}
/>
</li>
<li class={styles.bgListItem}>
<GradientPanel checked={data.value.background.kind === 'gradient'} gradient={gradient} onClick={() => onNav('gradient')} />
</li>
<li class={styles.bgListItem}>
<BackgroundImagePanel
checked={data.value.background.kind === 'userImage'}
onClick={() => onNav('image')}
data={data}
upload={onUpload}
browserTheme={browser}
/>
</li>
</ul>
);
}

Expand Down
Loading
Loading