diff --git a/special-pages/pages/new-tab/app/components/App.js b/special-pages/pages/new-tab/app/components/App.js
index fa41764f8..5fc03da43 100644
--- a/special-pages/pages/new-tab/app/components/App.js
+++ b/special-pages/pages/new-tab/app/components/App.js
@@ -1,4 +1,4 @@
-import { h } from 'preact';
+import { Fragment, h } from 'preact';
import cn from 'classnames';
import styles from './App.module.css';
import { useCustomizerDrawerSettings, usePlatformName } from '../settings.provider.js';
@@ -7,6 +7,7 @@ import { useGlobalDropzone } from '../dropzone.js';
import { Customizer, CustomizerButton, CustomizerMenuPositionedFixed, useContextMenu } from '../customizer/components/Customizer.js';
import { useDrawer, useDrawerControls } from './Drawer.js';
import { CustomizerDrawer } from '../customizer/components/CustomizerDrawer.js';
+import { BackgroundConsumer, BackgroundProvider } from './BackgroundProvider.js';
/**
* Renders the App component.
@@ -24,35 +25,42 @@ export function App({ children }) {
useContextMenu();
const { buttonRef, wrapperRef, visibility, displayChildren, hidden, buttonId, drawerId } = useDrawer();
- const { toggle, close } = useDrawerControls();
+ const { toggle } = useDrawerControls();
return (
-
-
-
-
-
- {customizerKind === 'menu' && }
- {customizerKind === 'drawer' && (
-
- )}
-
- {children}
-
-
+
{customizerKind === 'drawer' && (
-
+
+
+
)}
-
+
+
+
+
+
+ {customizerKind === 'menu' && }
+ {customizerKind === 'drawer' && (
+
+ )}
+
+ {children}
+
+
+ {customizerKind === 'drawer' && (
+
+ )}
+
+
);
}
diff --git a/special-pages/pages/new-tab/app/components/App.module.css b/special-pages/pages/new-tab/app/components/App.module.css
index 0569e5b0a..ad52a3d07 100644
--- a/special-pages/pages/new-tab/app/components/App.module.css
+++ b/special-pages/pages/new-tab/app/components/App.module.css
@@ -50,6 +50,7 @@ body:has([data-reset-layout="true"]) .tube {
}
}
+
.active {}
.aside {
@@ -69,5 +70,4 @@ body:has([data-reset-layout="true"]) .tube {
box-sizing: border-box;
height: 100vh;
width: var(--ntp-drawer-width);
- padding: var(--sp-2);
}
diff --git a/special-pages/pages/new-tab/app/components/BackgroundProvider.js b/special-pages/pages/new-tab/app/components/BackgroundProvider.js
new file mode 100644
index 000000000..fbf6f1b5c
--- /dev/null
+++ b/special-pages/pages/new-tab/app/components/BackgroundProvider.js
@@ -0,0 +1,100 @@
+import { createContext, Fragment, h } from 'preact';
+import styles from './BackgroundReceiver.module.css';
+import { values } from '../customizer/values.js';
+import { computed, signal } from '@preact/signals';
+import { useContext } from 'preact/hooks';
+import { CustomizerContext } from '../customizer/CustomizerProvider.js';
+
+/**
+ * @import { BackgroundVariant } from "../../types/new-tab"
+ */
+
+const BackgroundContext = createContext({
+ /** @type {import("@preact/signals").Signal} */
+ current: signal({ kind: 'default' }),
+});
+
+/**
+ * @param {object} props
+ * @param {import("preact").ComponentChild} props.children
+ */
+export function BackgroundProvider({ children }) {
+ const { data } = useContext(CustomizerContext);
+ const bg = computed(() => data.value.background);
+ return {children};
+}
+
+/**
+ *
+ */
+export function BackgroundConsumer() {
+ const { current } = useContext(BackgroundContext);
+ const background = current.value;
+ if (background === null) {
+ return ;
+ }
+ switch (background.kind) {
+ case 'hex': {
+ return (
+
+ );
+ }
+ case 'color': {
+ const color = values.colors[background.value];
+ return (
+
+ );
+ }
+ case 'gradient': {
+ const gradient = values.gradients[background.value];
+ return (
+
+
+
+
+ );
+ }
+ case 'userImage': {
+ const img = background.value;
+ return (
+
+ );
+ }
+ default: {
+ throw new Error('Unreachable!');
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/app/components/BackgroundReceiver.module.css b/special-pages/pages/new-tab/app/components/BackgroundReceiver.module.css
new file mode 100644
index 000000000..9d018328f
--- /dev/null
+++ b/special-pages/pages/new-tab/app/components/BackgroundReceiver.module.css
@@ -0,0 +1,8 @@
+.root {
+ position: fixed;
+ z-index: 0;
+ inset: 0;
+ width: 100vw;
+ height: 100vh;
+ transition: all .3s;
+}
diff --git a/special-pages/pages/new-tab/app/components/DismissButton.jsx b/special-pages/pages/new-tab/app/components/DismissButton.jsx
index 820d000af..6c746cd84 100644
--- a/special-pages/pages/new-tab/app/components/DismissButton.jsx
+++ b/special-pages/pages/new-tab/app/components/DismissButton.jsx
@@ -4,10 +4,10 @@ import { Cross } from './Icons';
import { useTypedTranslation } from '../types';
import styles from './DismissButton.module.css';
-/*
+/**
* @param {object} props
* @param {string} [props.className]
- * @param {() => void} props.onClick
+ * @param {() => void} [props.onClick]
*/
export function DismissButton({ className, onClick }) {
const { t } = useTypedTranslation();
diff --git a/special-pages/pages/new-tab/app/components/Icons.js b/special-pages/pages/new-tab/app/components/Icons.js
index dd0198ecd..8f2942c2d 100644
--- a/special-pages/pages/new-tab/app/components/Icons.js
+++ b/special-pages/pages/new-tab/app/components/Icons.js
@@ -82,3 +82,38 @@ export function Cross() {
);
}
+
+export function CircleCheck() {
+ return (
+
+ );
+}
+
+export function Picker() {
+ return (
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/CustomizerProvider.js b/special-pages/pages/new-tab/app/customizer/CustomizerProvider.js
new file mode 100644
index 000000000..c626edf53
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/CustomizerProvider.js
@@ -0,0 +1,92 @@
+import { createContext, h } from 'preact';
+import { useCallback } from 'preact/hooks';
+import { effect, signal, useSignal } from '@preact/signals';
+
+/**
+ * @typedef {import('../../types/new-tab.js').CustomizerData} CustomizerData
+ * @typedef {import('../service.hooks.js').State} State
+ * @typedef {import('../service.hooks.js').Events} Events
+ */
+
+/**
+ * These are the values exposed to consumers.
+ */
+export const CustomizerContext = createContext({
+ /** @type {import("@preact/signals").Signal} */
+ data: signal({
+ background: { kind: 'default' },
+ userImages: [],
+ theme: 'system',
+ }),
+ /** @type {(bg: CustomizerData['background']) => void} */
+ select: (bg) => {},
+ upload: () => {},
+ /**
+ * @type {(theme: import('../../types/new-tab').CustomizerData['theme']) => void}
+ */
+ setTheme: (theme) => {},
+ /**
+ * @type {(id: string) => void}
+ */
+ deleteImage: (id) => {},
+});
+
+/**
+ * A data provider that will use `RMFService` to fetch data, subscribe
+ * to updates and modify state.
+ *
+ * @param {Object} props
+ * @param {import("./customizer.service.js").CustomizerService} props.service
+ * @param {CustomizerData} props.initialData
+ * @param {import("preact").ComponentChild} props.children
+ */
+export function CustomizerProvider({ service, initialData, children }) {
+ // const [state, dispatch] = useReducer(withLog('RMFProvider', reducer), initial)
+ const data = useSignal(initialData);
+
+ effect(() => {
+ const unsub = service.onBackground((evt) => {
+ data.value = { ...data.value, background: evt.data };
+ });
+ const unsub1 = service.onTheme((evt) => {
+ data.value = { ...data.value, theme: evt.data };
+ });
+ const unsub2 = service.onImages((evt) => {
+ data.value = { ...data.value, userImages: evt.data };
+ });
+
+ return () => {
+ unsub();
+ unsub1();
+ unsub2();
+ };
+ });
+
+ /** @type {(bg: CustomizerData['background']) => void} */
+ const select = useCallback(
+ (bg) => {
+ service.setBackground(bg);
+ },
+ [service],
+ );
+
+ const upload = useCallback(() => {
+ service.upload();
+ }, [service]);
+
+ const setTheme = useCallback(
+ (theme) => {
+ service.setTheme(theme);
+ },
+ [service],
+ );
+
+ const deleteImage = useCallback(
+ (id) => {
+ service.deleteImage(id);
+ },
+ [service],
+ );
+
+ return {children};
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/BackgroundSection.js b/special-pages/pages/new-tab/app/customizer/components/BackgroundSection.js
new file mode 100644
index 000000000..f662908f2
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/components/BackgroundSection.js
@@ -0,0 +1,165 @@
+import { h, Fragment } from 'preact';
+import cn from 'classnames';
+
+import { values } from '../values.js';
+import styles from './CustomizerDrawerInner.module.css';
+import { CircleCheck } from '../../components/Icons.js';
+import { computed } from '@preact/signals';
+
+/**
+ * @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData } from '../../../types/new-tab.js'
+ */
+
+/**
+ * @param {object} props
+ * @param {import('@preact/signals').Signal} props.data
+ * @param {(target: 'color' | 'back' | 'image' | 'gradient') => void} props.onNav
+ * @param {() => void} props.onUpload
+ */
+export function BackgroundSection({ data, onNav, onUpload }) {
+ console.log(' RENDER:BackgroundSection?');
+ const color = values.colors.color11;
+ const gradient = values.gradients.gradient02;
+
+ return (
+
+
Background
+
+ -
+
+
+ -
+ onNav('color')} />
+
+ -
+ onNav('gradient')} />
+
+ -
+ onNav('image')} data={data} upload={onUpload} />
+
+
+
+ );
+}
+
+function DefaultPanel() {
+ return (
+ <>
+
+ Default
+ >
+ );
+}
+
+/**
+ * @param {object} props
+ * @param {() => void} props.onClick
+ * @param {typeof values.colors[keyof typeof values.colors]} props.color
+ */
+function ColorPanel(props) {
+ return (
+ <>
+
+ Solid Colors
+ >
+ );
+}
+
+/**
+ * @param {object} props
+ * @param {() => void} props.onClick
+ * @param {typeof values.gradients[keyof typeof values.gradients]} props.gradient
+ */
+function GradientPanel(props) {
+ return (
+ <>
+
+ Gradients
+ >
+ );
+}
+
+/**
+ * @param {object} props
+ * @param {() => void} props.onClick
+ * @param {() => void} props.upload
+ * @param {import('@preact/signals').Signal} props.data
+ */
+function BackgroundImagePanel(props) {
+ const empty = computed(() => props.data.value.userImages.length === 0);
+ const selectedImage = computed(() => {
+ const imageId = props.data.value.background.kind === 'userImage' ? props.data.value.background.value : null;
+ if (imageId !== null) {
+ const match = props.data.value.userImages.find((i) => i.id === imageId.id);
+ if (match) {
+ return match;
+ }
+ }
+ return null;
+ });
+
+ const firstImage = computed(() => {
+ return props.data.value.userImages[0] ?? null;
+ });
+
+ if (empty.value === true) {
+ return (
+
+
+ Add Background
+
+ );
+ }
+
+ if (selectedImage.value !== null) {
+ return (
+
+
+ My Backgrounds
+
+ );
+ }
+
+ return (
+
+
+ My Backgrounds
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/BrowserThemeSection.js b/special-pages/pages/new-tab/app/customizer/components/BrowserThemeSection.js
new file mode 100644
index 000000000..b8a7fbc0c
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/components/BrowserThemeSection.js
@@ -0,0 +1,60 @@
+import styles from './CustomizerDrawerInner.module.css';
+import cn from 'classnames';
+import { h } from 'preact';
+import { computed } from '@preact/signals';
+
+/**
+ * @param {object} props
+ * @param {import('@preact/signals').Signal} props.data
+ * @param {(theme: import('../../../types/new-tab').CustomizerData['theme']) => void} props.setTheme
+ */
+export function BrowserThemeSection(props) {
+ console.log(' RENDER:BrowserThemeSection?');
+ const current = computed(() => props.data.value.theme);
+ return (
+
+
Browser Theme
+
+ -
+
+ Light
+
+ -
+
+ Dark
+
+ -
+
+ System
+
+
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/ColorSelection.js b/special-pages/pages/new-tab/app/customizer/components/ColorSelection.js
new file mode 100644
index 000000000..76c9077a2
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/components/ColorSelection.js
@@ -0,0 +1,143 @@
+import { h, Fragment } from 'preact';
+import cn from 'classnames';
+
+import { values } from '../values.js';
+import styles from './CustomizerDrawerInner.module.css';
+import { Picker } from '../../components/Icons.js';
+import { computed, useSignal } from '@preact/signals';
+
+/**
+ * @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, PredefinedColor } from '../../../types/new-tab.js'
+ */
+
+/**
+ * @param {object} props
+ * @param {import("@preact/signals").Signal} props.data
+ * @param {(bg: CustomizerData['background']) => void} props.select
+ * @param {() => void} props.back
+ */
+export function ColorSelection({ data, select, back }) {
+ console.log(' RENDER:ColorSelection?');
+
+ function onClick(event) {
+ let target = /** @type {HTMLElement|null} */ (event.target);
+ while (target && target !== event.currentTarget) {
+ if (target.getAttribute('role') === 'radio') {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ if (target.getAttribute('aria-checked') === 'false') {
+ if (target.dataset.key) {
+ const value = /** @type {PredefinedColor} */ (target.dataset.key);
+ select({ kind: 'color', value });
+ } else {
+ console.warn('missing dataset.key');
+ }
+ } else {
+ console.log('ignoring click on selected color');
+ }
+ break;
+ } else {
+ target = target.parentElement;
+ }
+ }
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+const entries = Object.entries(values.colors);
+
+/**
+ * @param {object} props
+ * @param {import("@preact/signals").Signal} props.data
+ */
+function ColorGrid({ data }) {
+ const selected = computed(() => data.value.background.kind === 'color' && data.value.background.value);
+ return (
+
+ {entries.map(([key, entry]) => {
+ return (
+
+
+
+ );
+ })}
+
+ );
+}
+
+/**
+ * @param {object} props
+ * @param {import("@preact/signals").Signal} props.data
+ * @param {(bg: CustomizerData['background']) => void} props.select
+ */
+function PickerPanel({ data, select }) {
+ const peeked = data.peek();
+ const initialColor = peeked.background.kind === 'hex' ? peeked.background.value : '#000000';
+ const hex = useSignal(initialColor);
+ const hexSelected = computed(() => data.value.background.kind === 'hex');
+
+ return (
+
+
+
{
+ if (!(e.target instanceof HTMLInputElement)) {
+ return;
+ }
+ hex.value = e.target.value;
+ select({ kind: 'hex', value: hex.value });
+ }}
+ onClick={(e) => {
+ select({ kind: 'hex', value: hex.value });
+ }}
+ />
+
+
+
+
Show color picker
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/Customizer.examples.js b/special-pages/pages/new-tab/app/customizer/components/Customizer.examples.js
index 16eccf1a6..5d7ddce51 100644
--- a/special-pages/pages/new-tab/app/customizer/components/Customizer.examples.js
+++ b/special-pages/pages/new-tab/app/customizer/components/Customizer.examples.js
@@ -2,10 +2,66 @@ import { h, Fragment } from 'preact';
import { noop } from '../../utils.js';
import { CustomizerButton } from './Customizer.js';
import { VisibilityMenu } from './VisibilityMenu.js';
+import { BackgroundSection } from './BackgroundSection.js';
+import { ColorSelection } from './ColorSelection.js';
+import { GradientSelection } from './GradientSelection.js';
+import { useSignal } from '@preact/signals';
+import { ImageSelection } from './ImageSelection.js';
/** @type {Record import("preact").ComponentChild}>} */
-
export const customizerExamples = {
+ 'customizer.backgroundSection': {
+ factory: () => {
+ return (
+
+ {({ data, select }) => {
+ return ;
+ }}
+
+ );
+ },
+ },
+ 'customizer.colorSelection': {
+ factory: () => {
+ return (
+
+ {({ data, select }) => {
+ return ;
+ }}
+
+ );
+ },
+ },
+ 'customizer.gradientSelection': {
+ factory: () => {
+ return (
+
+ {({ data, select }) => {
+ return ;
+ }}
+
+ );
+ },
+ },
+ 'customizer.imageSelection': {
+ factory: () => {
+ return (
+
+ {({ data, select }) => {
+ return (
+
+ );
+ }}
+
+ );
+ },
+ },
'customizer-menu': {
factory: () => (
@@ -43,3 +99,20 @@ export const customizerExamples = {
function MaxContent({ children }) {
return {children}
;
}
+
+function Provider({ children }) {
+ /** @type {import('../../../types/new-tab.js').CustomizerData} */
+ const data = {
+ background: { kind: 'hex', value: '#17afa8' },
+ theme: 'system',
+ userImages: [],
+ };
+ const dataSignal = useSignal(data);
+ function select(bg) {
+ dataSignal.value = { ...dataSignal.value, background: bg };
+ }
+ function showPicker() {
+ console.log('no-op');
+ }
+ return children({ data: dataSignal, select, showPicker });
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawer.js b/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawer.js
index d8060db16..b1f388544 100644
--- a/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawer.js
+++ b/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawer.js
@@ -1,30 +1,26 @@
import { h } from 'preact';
import styles from './CustomizerDrawer.module.css';
-import { Suspense, lazy } from 'preact/compat';
import { useDrawerControls } from '../../components/Drawer.js';
-import { useEffect } from 'preact/hooks';
-
-// eslint-disable-next-line promise/prefer-await-to-then
-const CustomizerDrawerInner = lazy(() => import('./CustomizerDrawerInner').then((x) => x.CustomizerDrawerInner));
+import { useContext, useEffect } from 'preact/hooks';
+import { CustomizerContext } from '../CustomizerProvider.js';
+import { CustomizerDrawerInner } from './CustomizerDrawerInner.js';
/**
* @param {object} props
- * @param {object} props.onClose
- * @param {object} props.wrapperRef
* @param {import("@preact/signals").Signal} props.displayChildren
*/
-export function CustomizerDrawer({ onClose, displayChildren }) {
+export function CustomizerDrawer({ displayChildren }) {
const { open, close } = useDrawerControls();
useEffect(() => {
const checker = () => {
const shouldOpen = window.location.hash.startsWith('#/customizer');
- console.log({ shouldOpen });
if (shouldOpen) {
open();
} else {
close();
}
};
+
// check once on page load
checker();
@@ -34,14 +30,16 @@ export function CustomizerDrawer({ onClose, displayChildren }) {
window.removeEventListener('hashchange', checker);
};
}, []);
+
return (
-
- {displayChildren.value && (
- Loading...
}>
-
-
- )}
+
);
}
+
+function CustomizerConsumer() {
+ console.log('CustomizerConsumer');
+ const { data, select, upload, setTheme, deleteImage } = useContext(CustomizerContext);
+ return ;
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.js b/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.js
index 85e0031cd..dc9162a83 100644
--- a/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.js
+++ b/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.js
@@ -1,34 +1,65 @@
import { h } from 'preact';
+import cn from 'classnames';
import styles from './CustomizerDrawerInner.module.css';
-import { useState, useEffect } from 'preact/hooks';
-import { Customizer, getItems } from './Customizer';
-import { VisibilityMenu } from './VisibilityMenu.js';
+import { useDrawerControls } from '../../components/Drawer.js';
+import { BackgroundSection } from './BackgroundSection.js';
+import { BrowserThemeSection } from './BrowserThemeSection.js';
+import { VisibilityMenuSection } from './VisibilityMenuSection.js';
+import { ColorSelection } from './ColorSelection.js';
+import { useRef } from 'preact/hooks';
+import { GradientSelection } from './GradientSelection.js';
+import { useSignal } from '@preact/signals';
+import { ImageSelection } from './ImageSelection.js';
/**
- * @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem } from '../../../types/new-tab.js'
+ * @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData } from '../../../types/new-tab.js'
*/
-export function CustomizerDrawerInner() {
- const [rowData, setRowData] = useState(() => {
- const items = /** @type {import("./Customizer.js").VisibilityRowData[]} */ (getItems());
- return items;
- });
-
- useEffect(() => {
- function handler() {
- setRowData(getItems());
+/**
+ * @param {object} props
+ * @param {import('@preact/signals').Signal} props.data
+ * @param {(bg: CustomizerData['background']) => void} props.select
+ * @param {() => void} props.onUpload
+ * @param {(theme: import('../../../types/new-tab').CustomizerData['theme']) => void} props.setTheme
+ * @param {(id: string) => void} props.deleteImage
+ */
+export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage }) {
+ console.log(' RENDER:CustomizerDrawerInner?');
+ const { close } = useDrawerControls();
+ const ref = useRef(/** @type {any} */ (null));
+ const state = useSignal('home');
+ function onNav(nav) {
+ const curr = ref.current;
+ if (!curr) return;
+ if (ref.current instanceof HTMLDivElement) {
+ ref.current.style.gridTemplateAreas = "'col2 col1'";
}
- window.addEventListener(Customizer.UPDATE_EVENT, handler);
- return () => {
- window.removeEventListener(Customizer.UPDATE_EVENT, handler);
- };
- }, []);
-
+ state.value = nav;
+ }
+ function back() {
+ ref.current.style.gridTemplateAreas = "'col1 col2'";
+ state.value = 'home';
+ }
return (
-
Customize
-
-
+
+
+
+
+
+
+
+
+ {state.value === 'color' && }
+ {state.value === 'gradient' && }
+ {state.value === 'image' && (
+
+ )}
+
+
);
}
diff --git a/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.module.css b/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.module.css
index 028c29c61..6a8679c09 100644
--- a/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.module.css
+++ b/special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.module.css
@@ -4,6 +4,160 @@
animation-timing-function: ease-in-out;
animation-duration: .1s;
padding-block: 1rem;
+ display: grid;
+ grid-auto-rows: max-content;
+ grid-row-gap: var(--sp-4);
+ padding: var(--sp-4);
+ overflow: hidden;
+ font-size: var(--small-label-font-size);
+ line-height: var(--small-label-line-height);
+ font-weight: var(--small-label-font-weight);
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+}
+
+.cols {
+ display: grid;
+ grid-template-columns: 100% 100%;
+ grid-template-areas: 'col1 col2';
+ max-width: 100%;
+ overflow: hidden;
+ transition: all .3s;
+}
+.col {
+ width: 100%;
+ flex-shrink: 0;
+}
+.col1 {
+ width: 100%;
+ grid-area: col1
+}
+.col2 {
+ width: 100%;
+ grid-area: col2
+}
+.mainSections {
+ display: grid;
+ grid-row-gap: 36px;
+}
+.backBtn {
+ background: none;
+ border: none;
+ outline: none;
+ display: flex;
+ padding: 0;
+ align-items: center;
+ gap: 4px;
+
+ svg {
+ width: 16px; height: 16px; display: block
+ }
+
+ &:active {
+ opacity: .8;
+ }
+ &:focus-visible {
+ outline: 1px solid var(--ntp-focus-outline-color)
+ }
+}
+.section {
+ width: 100%;
+}
+.sectionBody {
+ margin-top: 16px;
+}
+.sectionTitle {
+ font-size: var(--title-3-em-font-size);
+ font-weight: var(--title-3-em-font-weight);
+ line-height: var(--title-3-em-line-height);
+}
+
+.bgList {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: max-content max-content;
+ grid-gap: 12px;
+}
+.bgListItem {
+ display: grid;
+ grid-row-gap: 6px;
+ white-space: nowrap;
+ position: relative;
+
+ &:hover {
+ .deleteBtn {
+ opacity: 1
+ }
+ }
+}
+.bgPanel {
+ display: grid;
+ aspect-ratio: 16/10;
+ border-radius: 4px;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ outline: none;
+
+ &[aria-checked="true"] {
+ outline: 3px solid var(--ntp-color-primary);
+ outline-offset: 2px;
+ }
+ &:focus-visible {
+ outline: 3px solid var(--ntp-focus-outline-color);
+ outline-offset: 2px;
+ }
+ &:active {
+ opacity: .9;
+ }
+}
+.bgPanelEmpty {
+ border: 1px solid rgba(0, 0, 0, 0.09);
+ background-color: rgba(0, 0, 0, 0.03);
+}
+.bgPanelOutlined {
+ border: 1px solid rgba(0, 0, 0, 0.09);
+ background-color: #FAFAFA;
+}
+.colorInputIcon {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ &, svg {
+ pointer-events: none;
+ }
+}
+.themeList {
+
+ display: flex;
+ gap: 18px;
+}
+.themeItem {
+ display: grid;
+ justify-items: center;
+ grid-row-gap: 6px;
+}
+.themeButton {
+ display: block;
+ width: 42px;
+ height: 42px;
+ border-radius: 50%;
+ border: 1px solid #0000001F;
+ &[aria-checked="true"] {
+ outline: 3px solid var(--ntp-color-primary);
+ outline-offset: 2px;
+ }
+ &:focus-visible {
+ outline: 3px solid var(--ntp-focus-outline-color);
+ outline-offset: 2px;
+ }
+ &:active {
+ opacity: .9;
+ }
}
@keyframes fade-in {
@@ -17,3 +171,10 @@
visibility: visible;
}
}
+
+.deleteBtn {
+ opacity: 0;
+ position: absolute;
+ top: 4px;
+ right: 4px;
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/GradientSelection.js b/special-pages/pages/new-tab/app/customizer/components/GradientSelection.js
new file mode 100644
index 000000000..4374a8229
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/components/GradientSelection.js
@@ -0,0 +1,95 @@
+import { h, Fragment } from 'preact';
+import cn from 'classnames';
+
+import { values } from '../values.js';
+import styles from './CustomizerDrawerInner.module.css';
+import { computed } from '@preact/signals';
+
+/**
+ * @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, PredefinedGradient } from '../../../types/new-tab.js'
+ */
+
+/**
+ * @param {object} props
+ * @param {import('@preact/signals').Signal} props.data
+ * @param {(bg: CustomizerData['background']) => void} props.select
+ * @param {() => void} props.back
+ */
+export function GradientSelection({ data, select, back }) {
+ // const gradient = values.gradients.gradient02;
+
+ function onClick(event) {
+ let target = /** @type {HTMLElement|null} */ (event.target);
+ while (target && target !== event.currentTarget) {
+ if (target.getAttribute('role') === 'radio') {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ if (target.getAttribute('aria-checked') === 'false') {
+ if (target.dataset.key) {
+ const value = /** @type {PredefinedGradient} */ (target.dataset.key);
+ select({ kind: 'gradient', value });
+ } else {
+ console.warn('missing dataset.key');
+ }
+ } else {
+ console.log('ignoring click on selected color');
+ }
+ break;
+ } else {
+ target = target.parentElement;
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+const entries = Object.entries(values.gradients);
+/**
+ * @param {object} props
+ * @param {import("@preact/signals").Signal} props.data
+ */
+function GradientGrid({ data }) {
+ const selected = computed(() => data.value.background.kind === 'gradient' && data.value.background.value);
+ return (
+
+ {entries.map(([key, entry]) => {
+ return (
+ -
+
+
+ );
+ })}
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/ImageSelection.js b/special-pages/pages/new-tab/app/customizer/components/ImageSelection.js
new file mode 100644
index 000000000..3a3dee2dc
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/components/ImageSelection.js
@@ -0,0 +1,104 @@
+import { h, Fragment } from 'preact';
+import cn from 'classnames';
+
+import styles from './CustomizerDrawerInner.module.css';
+import { computed } from '@preact/signals';
+import { DismissButton } from '../../components/DismissButton.jsx';
+
+/**
+ * @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, PredefinedGradient } from '../../../types/new-tab.js'
+ */
+
+/**
+ * @param {object} props
+ * @param {import('@preact/signals').Signal} props.data
+ * @param {(bg: CustomizerData['background']) => void} props.select
+ * @param {() => void} props.back
+ * @param {() => void} props.onUpload
+ * @param {(id: string) => void} props.deleteImage
+ */
+export function ImageSelection({ data, select, back, onUpload, deleteImage }) {
+ // const gradient = values.gradients.gradient02;
+
+ function onClick(event) {
+ let target = /** @type {HTMLElement|null} */ (event.target);
+ while (target && target !== event.currentTarget) {
+ if (target.getAttribute('role') === 'radio') {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ if (target.getAttribute('aria-checked') === 'false') {
+ if (target.dataset.key) {
+ const value = /** @type {string} */ (target.dataset.key);
+ const match = data.value.userImages.find((i) => i.id === value);
+ if (match) {
+ select({ kind: 'userImage', value: match });
+ }
+ } else {
+ console.warn('missing dataset.key');
+ }
+ } else {
+ console.log('ignoring click on selected color');
+ }
+ break;
+ } else {
+ target = target.parentElement;
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+/**
+ * @param {object} props
+ * @param {import("@preact/signals").Signal} props.data
+ * @param {(id: string) => void} props.deleteImage
+ */
+function ImageGrid({ data, deleteImage }) {
+ const selected = computed(() => data.value.background.kind === 'userImage' && data.value.background.value.id);
+ const entries = computed(() => {
+ return data.value.userImages;
+ });
+ return (
+
+ {entries.value.map((entry) => {
+ return (
+ -
+
+ deleteImage(entry.id)} />
+
+ );
+ })}
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.js b/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.js
index 938e11cb5..8a7c374aa 100644
--- a/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.js
+++ b/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.js
@@ -1,4 +1,5 @@
import { h } from 'preact';
+import cn from 'classnames';
import { useId } from 'preact/hooks';
import { DuckFoot, Shield } from '../../components/Icons.js';
@@ -22,7 +23,7 @@ export function VisibilityMenu({ rows, variant = 'popover' }) {
const MENU_ID = useId();
return (
-
+
{rows.map((row) => {
return (
-
diff --git a/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.module.css b/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.module.css
index f5d5f8b66..1fdeb6ff4 100644
--- a/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.module.css
+++ b/special-pages/pages/new-tab/app/customizer/components/VisibilityMenu.module.css
@@ -20,6 +20,11 @@
display: flex;
flex-direction: column;
gap: var(--sp-1);
+ font-size: var(--title-3-em-font-size);
+}
+
+.embedded {
+ font-size: var(--small-label-font-size);
}
.menuItemLabel {
@@ -27,7 +32,6 @@
align-items: center;
gap: 10px;
white-space: nowrap;
- font-size: var(--title-3-em-font-size);
height: calc(28 * var(--px-in-rem));
> * {
diff --git a/special-pages/pages/new-tab/app/customizer/components/VisibilityMenuSection.js b/special-pages/pages/new-tab/app/customizer/components/VisibilityMenuSection.js
new file mode 100644
index 000000000..4ffd4f059
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/components/VisibilityMenuSection.js
@@ -0,0 +1,32 @@
+import { useLayoutEffect, useState } from 'preact/hooks';
+import { Customizer, getItems } from './Customizer.js';
+import styles from './CustomizerDrawerInner.module.css';
+import { VisibilityMenu } from './VisibilityMenu.js';
+import { h } from 'preact';
+
+export function VisibilityMenuSection() {
+ console.log(' RENDER:VisibilityMenuSection');
+ const [rowData, setRowData] = useState(() => {
+ const items = /** @type {import("./Customizer.js").VisibilityRowData[]} */ (getItems());
+ return items;
+ });
+ useLayoutEffect(() => {
+ function handler() {
+ setRowData(getItems());
+ }
+
+ console.log('waitin..');
+ window.addEventListener(Customizer.UPDATE_EVENT, handler);
+ return () => {
+ window.removeEventListener(Customizer.UPDATE_EVENT, handler);
+ };
+ }, []);
+ return (
+
+ );
+}
diff --git a/special-pages/pages/new-tab/app/customizer/customizer.md b/special-pages/pages/new-tab/app/customizer/customizer.md
index 88f71c7ba..9c84460d4 100644
--- a/special-pages/pages/new-tab/app/customizer/customizer.md
+++ b/special-pages/pages/new-tab/app/customizer/customizer.md
@@ -22,13 +22,121 @@ title: Customizer
],
"widgetConfigs": [
{ "id": "favorites", "visibility": "visible" },
- { "id": "privacyStats", "visibility": "visible" },
+ { "id": "privacyStats", "visibility": "visible" }
],
"settings": {
"customizerDrawer": {
"state": "enabled"
}
+ },
+ "customizer": {
+ "userImages": [],
+ "theme": "dark",
+ "background": { "kind": "default" }
}
}
```
+## Initial Data
+
+- Add the key `customizer` to `initialSetup`
+- The data takes the following form: {@link "NewTab Messages".CustomizerData}
+- Example from `initialSetup`
+
+```json
+{
+ "...": "...",
+ "customizer": {
+ "userImages": [],
+ "theme": "dark",
+ "background": { "kind": "default" }
+ }
+}
+```
+
+## Subscriptions
+
+- {@link "NewTab Messages".CustomizerOnBackgroundUpdateSubscription `customizer_onBackgroundUpdate`}.
+ - Sends {@link "NewTab Messages".CustomizerOnBackgroundUpdateSubscribe} whenever needed.
+ - For example:
+ - ```json
+ {
+ "background": { "kind": "color", "value": "color01" }
+ }
+ ```
+ - ```json
+ {
+ "background": { "kind": "gradient", "value": "gradient01" }
+ }
+ ```
+ - ```json
+ {
+ "background": { "kind": "hex", "value": "#cacaca" }
+ }
+ ```
+ - ```json
+ {
+ "background": { "kind": "default" }
+ }
+ ```
+ - ```json
+ {
+ "background": {
+ "kind": "userImage",
+ "value": { "id": "abc", "src": "...", "thumb": "...", "colorScheme": "light" }
+ }
+ }
+ ```
+
+- {@link "NewTab Messages".CustomizerOnImagesUpdateSubscription `customizer_onImagesUpdate`}.
+ - Sends {@link "NewTab Messages".CustomizerOnImagesUpdateSubscribe} whenever needed.
+ - For example, this would be pushed into the page following a successful upload
+ - Note: In that situation, you'd send this followed by `customizer_onBackgroundUpdate` above
+ - For example:
+ - ```json
+ {
+ "userImages": [{"id": "abc", "src": "...", "thumb": "...", "colorScheme": "light" }]
+ }
+ ```
+
+- {@link "NewTab Messages".CustomizerOnThemeUpdateSubscription `customizer_onThemeUpdate`}.
+ - Sends {@link "NewTab Messages".CustomizerOnThemeUpdateSubscribe} whenever needed.
+ - For example:
+ - ```json
+ {
+ "theme": "system"
+ }
+ ```
+
+## Notifications
+
+- {@link "NewTab Messages".CustomizerSetBackgroundNotification `customizer_setBackground`}.
+ - Sends {@link "NewTab Messages".CustomizerSetBackgroundNotify} whenever needed.
+ - For example:
+ - ```json
+ {
+ "background": { "kind": "color", "value": "color01" }
+ }
+ ```
+
+- {@link "NewTab Messages".CustomizerSetThemeNotification `customizer_setTheme`}.
+ - Sends {@link "NewTab Messages".CustomizerSetBackgroundNotify} whenever needed.
+ - For example:
+ - ```json
+ {
+ "theme": "light"
+ }
+ ```
+
+- {@link "NewTab Messages".CustomizerUploadNotification `customizer_upload`}.
+ - Sent to trigger a file upload
+
+
+- {@link "NewTab Messages".CustomizerDeleteImageNotification `customizer_deleteImage`}.
+ - Sends {@link "NewTab Messages".CustomizerDeleteImageNotify} whenever needed.
+ - For example:
+ - ```json
+ {
+ "id": "abc"
+ }
+ ```
diff --git a/special-pages/pages/new-tab/app/customizer/customizer.service.js b/special-pages/pages/new-tab/app/customizer/customizer.service.js
new file mode 100644
index 000000000..c4adbc87f
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/customizer.service.js
@@ -0,0 +1,110 @@
+/**
+ * @typedef {import("../../types/new-tab.js").CustomizerData} CustomizerData
+ */
+import { Service } from '../service.js';
+
+/**
+ * @document ./customizer.md
+ */
+
+export class CustomizerService {
+ /**
+ * @param {import("../../src/js/index.js").NewTabPage} ntp - The internal data feed, expected to have a `subscribe` method.
+ * @param {CustomizerData} initial
+ * @internal
+ */
+ constructor(ntp, initial) {
+ this.ntp = ntp;
+ /** @type {Service} */
+ this.bgService = new Service(
+ {
+ subscribe: (cb) => ntp.messaging.subscribe('customizer_onBackgroundUpdate', cb),
+ persist: (data) => {
+ ntp.messaging.notify('customizer_setBackground', { background: data });
+ },
+ },
+ initial.background,
+ );
+ /** @type {Service} */
+ this.themeService = new Service(
+ {
+ subscribe: (cb) => ntp.messaging.subscribe('customizer_onThemeUpdate', cb),
+ },
+ initial.theme,
+ );
+ /** @type {Service} */
+ this.imagesService = new Service(
+ {
+ subscribe: (cb) => ntp.messaging.subscribe('customizer_onImagesUpdate', cb),
+ },
+ initial.userImages,
+ );
+ }
+
+ /**
+ * @internal
+ */
+ destroy() {
+ this.bgService.destroy();
+ this.themeService.destroy();
+ this.imagesService.destroy();
+ }
+
+ /**
+ * @param {(evt: {data: CustomizerData['background'], source: 'manual' | 'subscription'}) => void} cb
+ * @internal
+ */
+ onBackground(cb) {
+ return this.bgService.onData(cb);
+ }
+ /**
+ * @param {(evt: {data: CustomizerData['theme'], source: 'manual' | 'subscription'}) => void} cb
+ * @internal
+ */
+ onTheme(cb) {
+ return this.themeService.onData(cb);
+ }
+ /**
+ * @param {(evt: {data: CustomizerData['userImages'], source: 'manual' | 'subscription'}) => void} cb
+ * @internal
+ */
+ onImages(cb) {
+ return this.imagesService.onData(cb);
+ }
+
+ /**
+ * @param {CustomizerData['background']} bg
+ */
+ setBackground(bg) {
+ this.bgService.update((data) => {
+ return bg;
+ });
+ }
+
+ /**
+ * @param {string} id
+ */
+ deleteImage(id) {
+ this.imagesService.update((data) => {
+ return data.filter((img) => img.id !== id);
+ });
+ this.ntp.messaging.notify('customizer_deleteImage', { id });
+ }
+
+ /**
+ *
+ */
+ upload() {
+ this.ntp.messaging.notify('customizer_upload');
+ }
+
+ /**
+ * @param {import('../../types/new-tab').CustomizerData['theme']} theme
+ */
+ setTheme(theme) {
+ this.themeService.update((_data) => {
+ return theme;
+ });
+ this.ntp.messaging.notify('customizer_setTheme', { theme });
+ }
+}
diff --git a/special-pages/pages/new-tab/app/customizer/values.js b/special-pages/pages/new-tab/app/customizer/values.js
new file mode 100644
index 000000000..c49d10dc7
--- /dev/null
+++ b/special-pages/pages/new-tab/app/customizer/values.js
@@ -0,0 +1,153 @@
+/**
+ * @import { PredefinedColor, PredefinedGradient, BackgroundColorScheme, UserImage } from "../../types/new-tab"
+ * @type {{
+ * colors: Record,
+ * gradients: Record
+ * userImages: Record<'01' | '02' | '03', UserImage>
+ * }}
+ */
+export const values = {
+ colors: {
+ color01: { hex: '#000000', colorScheme: 'dark' },
+ color02: { hex: '#342E42', colorScheme: 'dark' },
+ color03: { hex: '#4D5F7F', colorScheme: 'dark' },
+ color04: { hex: '#E28499', colorScheme: 'light' },
+ color05: { hex: '#F7DEE5', colorScheme: 'light' },
+ color06: { hex: '#D55154', colorScheme: 'dark' },
+ color07: { hex: '#E5724F', colorScheme: 'dark' },
+ color08: { hex: '#F3BB44', colorScheme: 'light' },
+ color09: { hex: '#E9DCCD', colorScheme: 'light' },
+ color10: { hex: '#5BC787', colorScheme: 'light' },
+ color11: { hex: '#4594A7', colorScheme: 'dark' },
+ color12: { hex: '#B5E2CE', colorScheme: 'light' },
+ color13: { hex: '#E4DEF2', colorScheme: 'light' },
+ color14: { hex: '#B79ED4', colorScheme: 'light' },
+ color15: { hex: '#5552AC', colorScheme: 'dark' },
+ color16: { hex: '#75B9F0', colorScheme: 'light' },
+ color17: { hex: '#577DE4', colorScheme: 'dark' },
+ color18: { hex: '#DBDDDF', colorScheme: 'light' },
+ color19: { hex: '#9A979D', colorScheme: 'dark' },
+ },
+ gradients: {
+ gradient01: { path: 'gradients/gradient01.svg', colorScheme: 'light' },
+ gradient02: { path: 'gradients/gradient02.svg', colorScheme: 'light' },
+ gradient03: { path: 'gradients/gradient03.svg', colorScheme: 'light' },
+ gradient04: { path: 'gradients/gradient04.svg', colorScheme: 'light' },
+ gradient05: { path: 'gradients/gradient05.svg', colorScheme: 'dark' },
+ gradient06: { path: 'gradients/gradient06.svg', colorScheme: 'dark' },
+ gradient07: { path: 'gradients/gradient07.svg', colorScheme: 'dark' },
+ gradient08: { path: 'gradients/gradient08.svg', colorScheme: 'dark' },
+ },
+ userImages: {
+ '01': {
+ colorScheme: 'dark',
+ id: '01',
+ src: 'backgrounds/bg-01.jpg',
+ thumb: 'backgrounds/bg-01-thumb.jpg',
+ },
+ '02': {
+ colorScheme: 'light',
+ id: '02',
+ src: 'backgrounds/bg-02.jpg',
+ thumb: 'backgrounds/bg-02-thumb.jpg',
+ },
+ '03': {
+ colorScheme: 'light',
+ id: '03',
+ src: 'backgrounds/bg-03.jpg',
+ thumb: 'backgrounds/bg-03-thumb.jpg',
+ },
+ },
+};
+
+/**
+ * Determines if a light or dark theme should be used based on background color
+ * @param {string} backgroundColor - HEX color code (6 or 8 digits)
+ * @returns {'light' | 'dark'} - Returns 'light' or 'dark'
+ */
+export function detectTheme(backgroundColor) {
+ // Remove # if present and handle both 6 and 8 digit hex codes
+ const hex = backgroundColor.replace('#', '');
+
+ // Extract RGB values
+ const r = parseInt(hex.slice(0, 2), 16);
+ const g = parseInt(hex.slice(2, 4), 16);
+ const b = parseInt(hex.slice(4, 6), 16);
+
+ // Calculate relative luminance using sRGB coefficients
+ // Using the formula from WCAG 2.0
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+
+ // Choose theme based on luminance
+ // 128 is the middle value (255/2)
+ return luminance < 128 ? 'dark' : 'light';
+}
+
+// Test cases using Node's built-in assert
+// const testCases = [
+// {
+// input: '#FFFFFF',
+// expected: 'light',
+// description: 'Pure white should be light theme',
+// },
+// {
+// input: '#000000',
+// expected: 'dark',
+// description: 'Pure black should be dark theme',
+// },
+// {
+// input: '7B7B7B',
+// expected: 'dark',
+// description: 'Medium gray should be dark theme',
+// },
+// {
+// input: 'FFFFFF00',
+// expected: 'light',
+// description: 'White with alpha should be light theme',
+// },
+// {
+// input: '#1E90FF',
+// expected: 'dark',
+// description: 'Dodger blue should be dark theme',
+// },
+// {
+// input: '#FFD700',
+// expected: 'light',
+// description: 'Gold should be light theme',
+// },
+// {
+// input: '#98FB98',
+// expected: 'light',
+// description: 'Pale green should be light theme',
+// },
+// {
+// input: '#800080',
+// expected: 'dark',
+// description: 'Purple should be dark theme',
+// },
+// {
+// input: '#FFA07A',
+// expected: 'light',
+// description: 'Light salmon should be light theme',
+// },
+// {
+// input: '#2F4F4F',
+// expected: 'dark',
+// description: 'Dark slate gray should be dark theme',
+// },
+// ];
+
+// Run tests
+// console.log('Running tests...\n');
+// testCases.forEach((testCase, index) => {
+// try {
+// const result = detectTheme(testCase.input);
+// assert.strictEqual(result, testCase.expected);
+// console.log(`✓ Test ${index + 1}: ${testCase.description}`);
+// } catch (error) {
+// console.error(`✗ Test ${index + 1}: ${testCase.description}`);
+// console.error(` Expected: ${testCase.expected}`);
+// // console.error(` Received: ${result}`);
+// console.error(` Input: ${testCase.input}\n`);
+// }
+// });
diff --git a/special-pages/pages/new-tab/app/index.js b/special-pages/pages/new-tab/app/index.js
index bcf5e1653..8fd88cfb8 100644
--- a/special-pages/pages/new-tab/app/index.js
+++ b/special-pages/pages/new-tab/app/index.js
@@ -13,6 +13,8 @@ import { Settings } from './settings.js';
import { Components } from './components/Components.jsx';
import { widgetEntryPoint } from './widget-list/WidgetList.js';
import { callWithRetry } from '../../../shared/call-with-retry.js';
+import { CustomizerProvider } from './customizer/CustomizerProvider.js';
+import { CustomizerService } from './customizer/customizer.service.js';
/**
* @import {Telemetry} from "./telemetry/telemetry.js"
@@ -82,6 +84,7 @@ export async function init(root, messaging, telemetry, baseEnvironment) {
// Create an instance of the global widget api
const widgetConfigAPI = new WidgetConfigService(messaging, init.widgetConfigs);
+ const customizerApi = new CustomizerService(messaging, init.customizer);
render(
-
-
-
+
+
+
+
+
diff --git a/special-pages/pages/new-tab/app/mock-transport.js b/special-pages/pages/new-tab/app/mock-transport.js
index dfc53b031..1af343877 100644
--- a/special-pages/pages/new-tab/app/mock-transport.js
+++ b/special-pages/pages/new-tab/app/mock-transport.js
@@ -5,6 +5,7 @@ import { rmfDataExamples } from './remote-messaging-framework/mocks/rmf.data.js'
import { favorites, gen } from './favorites/mocks/favorites.data.js';
import { updateNotificationExamples } from './update-notification/mocks/update-notification.data.js';
import { variants as nextSteps } from './next-steps/nextsteps.data.js';
+import { values } from './customizer/values.js';
/**
* @typedef {import('../types/new-tab').Favorite} Favorite
@@ -465,6 +466,7 @@ export function mockTransport() {
env: 'development',
locale: 'en',
updateNotification,
+ customizer: customizerData(),
};
return Promise.resolve(initial);
@@ -477,6 +479,56 @@ export function mockTransport() {
});
}
+/** @type {()=>import('../types/new-tab').CustomizerData} */
+function customizerData() {
+ /** @type {import('../types/new-tab').CustomizerData} */
+ const customizer = {
+ userImages: [],
+ theme: 'dark',
+ background: { kind: 'default' },
+ };
+
+ if (url.searchParams.has('background')) {
+ const value = url.searchParams.get('background');
+ if (value && value in values.colors) {
+ customizer.background = {
+ kind: 'color',
+ value: /** @type {import('../types/new-tab').PredefinedColor} */ (value),
+ };
+ } else if (value && value in values.gradients) {
+ customizer.background = {
+ kind: 'gradient',
+ value: /** @type {import('../types/new-tab').PredefinedGradient} */ (value),
+ };
+ } else if (value && value.startsWith('hex:')) {
+ const hex = value.slice(4);
+ if (hex.length === 6 || hex.length === 8) {
+ customizer.background = {
+ kind: 'hex',
+ value: `#${hex.slice(0, 6)}`,
+ };
+ } else {
+ console.warn('invalid hex values');
+ }
+ } else if (value && value.startsWith('userImage:')) {
+ const image = value.slice(10);
+ if (image in values.userImages) {
+ customizer.background = {
+ kind: 'userImage',
+ value: values.userImages[image],
+ };
+ } else {
+ console.warn('unknown user image');
+ }
+ }
+ }
+
+ if (url.searchParams.has('userImages')) {
+ customizer.userImages = [values.userImages['01'], values.userImages['02'], values.userImages['03']];
+ }
+ return customizer;
+}
+
/**
* @template {{id: string}} T
* @param {T[]} array
diff --git a/special-pages/pages/new-tab/app/privacy-stats/components/PrivacyStats.module.css b/special-pages/pages/new-tab/app/privacy-stats/components/PrivacyStats.module.css
index 9f2dd8580..0d3f4361e 100644
--- a/special-pages/pages/new-tab/app/privacy-stats/components/PrivacyStats.module.css
+++ b/special-pages/pages/new-tab/app/privacy-stats/components/PrivacyStats.module.css
@@ -1,5 +1,6 @@
.root {
background: var(--ntp-surface-background-color);
+ backdrop-filter: blur(48px);
border: 1px solid var(--ntp-surface-border-color);
padding: var(--sp-6);
border-radius: var(--border-radius-lg);
diff --git a/special-pages/pages/new-tab/app/styles/ntp-theme.css b/special-pages/pages/new-tab/app/styles/ntp-theme.css
index 8be2d183c..3417b08dc 100644
--- a/special-pages/pages/new-tab/app/styles/ntp-theme.css
+++ b/special-pages/pages/new-tab/app/styles/ntp-theme.css
@@ -1,6 +1,6 @@
:root {
--ntp-background-color: white;
- --ntp-surface-background-color: white;
+ --ntp-surface-background-color: rgba(255, 255, 255, 0.30);
--ntp-surfaces-panel-background-color: white;
--ntp-surface-border-color: var(--color-black-at-6);
--ntp-text-normal: var(--color-black-at-84);
@@ -25,6 +25,11 @@
--title-3-em-font-weight: 590;
--title-3-em-line-height: 20px;
+ /* label small */
+ --small-label-font-size: 11px;
+ --small-label-font-weight: 400;
+ --small-label-line-height: 11px;
+
--ntp-focus-outline-color: black;
--border-radius-lg: 12px;
--border-radius-md: 8px;
@@ -36,7 +41,7 @@
@media (prefers-color-scheme: dark) {
--ntp-background-color: var(--color-gray-85);
- --ntp-surface-background-color: #2a2a2a;
+ --ntp-surface-background-color: rgba(0, 0, 0, 0.18);
--ntp-surfaces-panel-background-color: #222222;
--ntp-surface-border-color: var(--color-white-at-6);
--ntp-text-normal: var(--color-white-at-84);
diff --git a/special-pages/pages/new-tab/integration-tests/new-tab.page.js b/special-pages/pages/new-tab/integration-tests/new-tab.page.js
index 3868a8b6e..9426b5289 100644
--- a/special-pages/pages/new-tab/integration-tests/new-tab.page.js
+++ b/special-pages/pages/new-tab/integration-tests/new-tab.page.js
@@ -40,6 +40,7 @@ export class NewtabPage {
name: this.platform.name || 'windows',
},
updateNotification: { content: null },
+ customizer: { theme: 'system', userImages: [], background: { kind: 'default' } },
},
stats_getConfig: {},
stats_getData: {},
diff --git a/special-pages/pages/new-tab/messages/customizer_deleteImage.notify.json b/special-pages/pages/new-tab/messages/customizer_deleteImage.notify.json
new file mode 100644
index 000000000..7d778cdd7
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_deleteImage.notify.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_getData.request.json b/special-pages/pages/new-tab/messages/customizer_getData.request.json
new file mode 100644
index 000000000..0af74a319
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_getData.request.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_getData.response.json b/special-pages/pages/new-tab/messages/customizer_getData.response.json
new file mode 100644
index 000000000..60d1826a3
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_getData.response.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "allOf": [
+ {
+ "$ref": "./types/customizer-data.json"
+ }
+ ]
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_onBackgroundUpdate.subscribe.json b/special-pages/pages/new-tab/messages/customizer_onBackgroundUpdate.subscribe.json
new file mode 100644
index 000000000..2fc721e9a
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_onBackgroundUpdate.subscribe.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": ["background"],
+ "properties": {
+ "background": { "$ref": "./types/background.json" }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_onImagesUpdate.subscribe.json b/special-pages/pages/new-tab/messages/customizer_onImagesUpdate.subscribe.json
new file mode 100644
index 000000000..a7471a245
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_onImagesUpdate.subscribe.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [
+ "userImages"
+ ],
+ "properties": {
+ "userImages": {
+ "type": "array",
+ "items": {
+ "$ref": "./types/user-image.json"
+ }
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_onThemeUpdate.subscribe.json b/special-pages/pages/new-tab/messages/customizer_onThemeUpdate.subscribe.json
new file mode 100644
index 000000000..a3ee380b1
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_onThemeUpdate.subscribe.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [
+ "theme"
+ ],
+ "properties": {
+ "theme": {
+ "$ref": "types/browser-theme.json"
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_setBackground.notify.json b/special-pages/pages/new-tab/messages/customizer_setBackground.notify.json
new file mode 100644
index 000000000..b3df277aa
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_setBackground.notify.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [
+ "background"
+ ],
+ "properties": {
+ "background": {
+ "$ref": "./types/background.json"
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_setTheme.notify.json b/special-pages/pages/new-tab/messages/customizer_setTheme.notify.json
new file mode 100644
index 000000000..8acd7bb2a
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_setTheme.notify.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": ["theme"],
+ "properties": {
+ "theme": {
+ "$ref": "types/browser-theme.json"
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/customizer_upload.notify.json b/special-pages/pages/new-tab/messages/customizer_upload.notify.json
new file mode 100644
index 000000000..0af74a319
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/customizer_upload.notify.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
diff --git a/special-pages/pages/new-tab/messages/examples/widgets.js b/special-pages/pages/new-tab/messages/examples/widgets.js
index e3ff48d08..7c903487b 100644
--- a/special-pages/pages/new-tab/messages/examples/widgets.js
+++ b/special-pages/pages/new-tab/messages/examples/widgets.js
@@ -41,6 +41,7 @@ const initialSetupResponse = {
locale: 'en',
platform: { name: 'windows' },
updateNotification: { content: null },
+ customizer: { theme: 'system', userImages: [], background: { kind: 'default' } },
};
export {};
diff --git a/special-pages/pages/new-tab/messages/initialSetup.response.json b/special-pages/pages/new-tab/messages/initialSetup.response.json
index b08fb9352..95a7ba5a1 100644
--- a/special-pages/pages/new-tab/messages/initialSetup.response.json
+++ b/special-pages/pages/new-tab/messages/initialSetup.response.json
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
- "required": ["widgets", "widgetConfigs", "locale", "env", "platform", "updateNotification"],
+ "required": ["widgets", "widgetConfigs", "locale", "env", "platform", "updateNotification", "customizer"],
"properties": {
"widgets": {
"$ref": "types/widget-list.json"
@@ -29,6 +29,9 @@
}
}
},
+ "customizer": {
+ "$ref": "./types/customizer-data.json"
+ },
"updateNotification": {
"oneOf": [
{
diff --git a/special-pages/pages/new-tab/messages/types/background.json b/special-pages/pages/new-tab/messages/types/background.json
new file mode 100644
index 000000000..fef143870
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/types/background.json
@@ -0,0 +1,80 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Background Variant",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": ["kind"],
+ "title": "Default Background",
+ "properties": {
+ "kind": {
+ "const": "default"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "kind",
+ "value"
+ ],
+ "title": "Solid Color Background",
+ "properties": {
+ "kind": {
+ "const": "color"
+ },
+ "value": {
+ "$ref": "./colors.json#/definitions/colors"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "kind",
+ "value"
+ ],
+ "title": "Hex Value Background",
+ "properties": {
+ "kind": {
+ "const": "hex"
+ },
+ "value": {
+ "type": "string"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "kind",
+ "value"
+ ],
+ "title": "Gradient Background",
+ "properties": {
+ "kind": {
+ "const": "gradient"
+ },
+ "value": {
+ "$ref": "./colors.json#/definitions/gradients"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "required": [
+ "kind",
+ "value"
+ ],
+ "title": "User Image Background",
+ "properties": {
+ "kind": {
+ "const": "userImage"
+ },
+ "value": {
+ "$ref": "./user-image.json"
+ }
+ }
+ }
+ ]
+}
diff --git a/special-pages/pages/new-tab/messages/types/browser-theme.json b/special-pages/pages/new-tab/messages/types/browser-theme.json
new file mode 100644
index 000000000..1ce532afb
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/types/browser-theme.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Browser Theme",
+ "enum": [
+ "light",
+ "dark",
+ "system"
+ ]
+}
diff --git a/special-pages/pages/new-tab/messages/types/colors.json b/special-pages/pages/new-tab/messages/types/colors.json
new file mode 100644
index 000000000..05f44d2a0
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/types/colors.json
@@ -0,0 +1,47 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "definitions": {
+ "gradients": {
+ "title": "Predefined Gradient",
+ "enum": [
+ "gradient01",
+ "gradient02",
+ "gradient03",
+ "gradient04",
+ "gradient05",
+ "gradient06",
+ "gradient07",
+ "gradient08"
+ ]
+ },
+ "colors": {
+ "title": "Predefined Color",
+ "enum": [
+ "color01",
+ "color02",
+ "color03",
+ "color04",
+ "color05",
+ "color06",
+ "color07",
+ "color08",
+ "color09",
+ "color10",
+ "color11",
+ "color12",
+ "color13",
+ "color14",
+ "color15",
+ "color16",
+ "color17",
+ "color18",
+ "color19"
+ ]
+ },
+ "colorScheme": {
+ "title": "Background Color Scheme",
+ "description": "Note: this is different to the Browser Theme",
+ "enum": ["light", "dark"]
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/types/customizer-data.json b/special-pages/pages/new-tab/messages/types/customizer-data.json
new file mode 100644
index 000000000..1ca4fbb83
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/types/customizer-data.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Customizer Data",
+ "type": "object",
+ "required": [
+ "background",
+ "theme",
+ "userImages"
+ ],
+ "properties": {
+ "background": {"$ref": "./background.json"},
+ "theme": { "$ref": "./browser-theme.json" },
+ "userImages": {
+ "type": "array",
+ "items": {
+ "$ref": "./user-image.json"
+ }
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/messages/types/user-image.json b/special-pages/pages/new-tab/messages/types/user-image.json
new file mode 100644
index 000000000..1736f4413
--- /dev/null
+++ b/special-pages/pages/new-tab/messages/types/user-image.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "UserImage",
+ "type": "object",
+ "required": ["id", "colorScheme", "src", "thumb"],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "src": {
+ "type": "string"
+ },
+ "thumb": {
+ "type": "string"
+ },
+ "colorScheme": {
+ "$ref": "./colors.json#/definitions/colorScheme"
+ }
+ }
+}
diff --git a/special-pages/pages/new-tab/src/backgrounds/bg-01-thumb.jpg b/special-pages/pages/new-tab/src/backgrounds/bg-01-thumb.jpg
new file mode 100644
index 000000000..6e0da17af
Binary files /dev/null and b/special-pages/pages/new-tab/src/backgrounds/bg-01-thumb.jpg differ
diff --git a/special-pages/pages/new-tab/src/backgrounds/bg-01.jpg b/special-pages/pages/new-tab/src/backgrounds/bg-01.jpg
new file mode 100644
index 000000000..a4af5d0fb
Binary files /dev/null and b/special-pages/pages/new-tab/src/backgrounds/bg-01.jpg differ
diff --git a/special-pages/pages/new-tab/src/backgrounds/bg-02-thumb.jpg b/special-pages/pages/new-tab/src/backgrounds/bg-02-thumb.jpg
new file mode 100644
index 000000000..d39ec6fcb
Binary files /dev/null and b/special-pages/pages/new-tab/src/backgrounds/bg-02-thumb.jpg differ
diff --git a/special-pages/pages/new-tab/src/backgrounds/bg-02.jpg b/special-pages/pages/new-tab/src/backgrounds/bg-02.jpg
new file mode 100644
index 000000000..c1bf4a0e3
Binary files /dev/null and b/special-pages/pages/new-tab/src/backgrounds/bg-02.jpg differ
diff --git a/special-pages/pages/new-tab/src/backgrounds/bg-03-thumb.jpg b/special-pages/pages/new-tab/src/backgrounds/bg-03-thumb.jpg
new file mode 100644
index 000000000..06bf64d7a
Binary files /dev/null and b/special-pages/pages/new-tab/src/backgrounds/bg-03-thumb.jpg differ
diff --git a/special-pages/pages/new-tab/src/backgrounds/bg-03.jpg b/special-pages/pages/new-tab/src/backgrounds/bg-03.jpg
new file mode 100644
index 000000000..c7265d5f2
Binary files /dev/null and b/special-pages/pages/new-tab/src/backgrounds/bg-03.jpg differ
diff --git a/special-pages/pages/new-tab/src/gradients/gradient01.svg b/special-pages/pages/new-tab/src/gradients/gradient01.svg
new file mode 100644
index 000000000..a9fcd0672
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient01.svg
@@ -0,0 +1,46 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient02.svg b/special-pages/pages/new-tab/src/gradients/gradient02.svg
new file mode 100644
index 000000000..de7ab3b7e
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient02.svg
@@ -0,0 +1,46 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient03.svg b/special-pages/pages/new-tab/src/gradients/gradient03.svg
new file mode 100644
index 000000000..0639b84c2
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient03.svg
@@ -0,0 +1,31 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient04.svg b/special-pages/pages/new-tab/src/gradients/gradient04.svg
new file mode 100644
index 000000000..9843eade1
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient04.svg
@@ -0,0 +1,31 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient05.svg b/special-pages/pages/new-tab/src/gradients/gradient05.svg
new file mode 100644
index 000000000..eee5ce1a3
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient05.svg
@@ -0,0 +1,46 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient06.svg b/special-pages/pages/new-tab/src/gradients/gradient06.svg
new file mode 100644
index 000000000..a91178e5f
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient06.svg
@@ -0,0 +1,31 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient07.svg b/special-pages/pages/new-tab/src/gradients/gradient07.svg
new file mode 100644
index 000000000..1527605f9
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient07.svg
@@ -0,0 +1,51 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/gradient08.svg b/special-pages/pages/new-tab/src/gradients/gradient08.svg
new file mode 100644
index 000000000..a7c77f9a4
--- /dev/null
+++ b/special-pages/pages/new-tab/src/gradients/gradient08.svg
@@ -0,0 +1,36 @@
+
diff --git a/special-pages/pages/new-tab/src/gradients/grain.png b/special-pages/pages/new-tab/src/gradients/grain.png
new file mode 100644
index 000000000..8d3ef5245
Binary files /dev/null and b/special-pages/pages/new-tab/src/gradients/grain.png differ
diff --git a/special-pages/pages/new-tab/types/new-tab.ts b/special-pages/pages/new-tab/types/new-tab.ts
index cae06d2cc..9f19c301d 100644
--- a/special-pages/pages/new-tab/types/new-tab.ts
+++ b/special-pages/pages/new-tab/types/new-tab.ts
@@ -6,6 +6,46 @@
* @module NewTab Messages
*/
+export type BackgroundVariant =
+ | DefaultBackground
+ | SolidColorBackground
+ | HexValueBackground
+ | GradientBackground
+ | UserImageBackground;
+export type PredefinedColor =
+ | "color01"
+ | "color02"
+ | "color03"
+ | "color04"
+ | "color05"
+ | "color06"
+ | "color07"
+ | "color08"
+ | "color09"
+ | "color10"
+ | "color11"
+ | "color12"
+ | "color13"
+ | "color14"
+ | "color15"
+ | "color16"
+ | "color17"
+ | "color18"
+ | "color19";
+export type PredefinedGradient =
+ | "gradient01"
+ | "gradient02"
+ | "gradient03"
+ | "gradient04"
+ | "gradient05"
+ | "gradient06"
+ | "gradient07"
+ | "gradient08";
+/**
+ * Note: this is different to the Browser Theme
+ */
+export type BackgroundColorScheme = "light" | "dark";
+export type BrowserTheme = "light" | "dark" | "system";
/**
* Represents the expansion state of a widget
*/
@@ -45,6 +85,10 @@ export type RMFIcon = "Announce" | "DDGAnnounce" | "CriticalUpdate" | "AppUpdate
export interface NewTabMessages {
notifications:
| ContextMenuNotification
+ | CustomizerDeleteImageNotification
+ | CustomizerSetBackgroundNotification
+ | CustomizerSetThemeNotification
+ | CustomizerUploadNotification
| FavoritesAddNotification
| FavoritesMoveNotification
| FavoritesOpenNotification
@@ -65,6 +109,7 @@ export interface NewTabMessages {
| UpdateNotificationDismissNotification
| WidgetsSetConfigNotification;
requests:
+ | CustomizerGetDataRequest
| FavoritesGetConfigRequest
| FavoritesGetDataRequest
| InitialSetupRequest
@@ -74,6 +119,9 @@ export interface NewTabMessages {
| StatsGetConfigRequest
| StatsGetDataRequest;
subscriptions:
+ | CustomizerOnBackgroundUpdateSubscription
+ | CustomizerOnImagesUpdateSubscription
+ | CustomizerOnThemeUpdateSubscription
| FavoritesOnConfigUpdateSubscription
| FavoritesOnDataUpdateSubscription
| NextStepsOnConfigUpdateSubscription
@@ -101,6 +149,67 @@ export interface VisibilityMenuItem {
*/
title: string;
}
+/**
+ * Generated from @see "../messages/customizer_deleteImage.notify.json"
+ */
+export interface CustomizerDeleteImageNotification {
+ method: "customizer_deleteImage";
+ params: CustomizerDeleteImageNotify;
+}
+export interface CustomizerDeleteImageNotify {
+ id: string;
+}
+/**
+ * Generated from @see "../messages/customizer_setBackground.notify.json"
+ */
+export interface CustomizerSetBackgroundNotification {
+ method: "customizer_setBackground";
+ params: CustomizerSetBackgroundNotify;
+}
+export interface CustomizerSetBackgroundNotify {
+ background: BackgroundVariant;
+}
+export interface DefaultBackground {
+ kind: "default";
+}
+export interface SolidColorBackground {
+ kind: "color";
+ value: PredefinedColor;
+}
+export interface HexValueBackground {
+ kind: "hex";
+ value: string;
+}
+export interface GradientBackground {
+ kind: "gradient";
+ value: PredefinedGradient;
+}
+export interface UserImageBackground {
+ kind: "userImage";
+ value: UserImage;
+}
+export interface UserImage {
+ id: string;
+ src: string;
+ thumb: string;
+ colorScheme: BackgroundColorScheme;
+}
+/**
+ * Generated from @see "../messages/customizer_setTheme.notify.json"
+ */
+export interface CustomizerSetThemeNotification {
+ method: "customizer_setTheme";
+ params: CustomizerSetThemeNotify;
+}
+export interface CustomizerSetThemeNotify {
+ theme: BrowserTheme;
+}
+/**
+ * Generated from @see "../messages/customizer_upload.notify.json"
+ */
+export interface CustomizerUploadNotification {
+ method: "customizer_upload";
+}
/**
* Generated from @see "../messages/favorites_add.notify.json"
*/
@@ -326,6 +435,18 @@ export interface WidgetConfigItem {
id: string;
visibility: WidgetVisibility;
}
+/**
+ * Generated from @see "../messages/customizer_getData.request.json"
+ */
+export interface CustomizerGetDataRequest {
+ method: "customizer_getData";
+ result: CustomizerData;
+}
+export interface CustomizerData {
+ background: BackgroundVariant;
+ theme: BrowserTheme;
+ userImages: UserImage[];
+}
/**
* Generated from @see "../messages/favorites_getConfig.request.json"
*/
@@ -369,6 +490,7 @@ export interface InitialSetupResponse {
platform: {
name: "macos" | "windows" | "android" | "ios" | "integration";
};
+ customizer: CustomizerData;
updateNotification: null | UpdateNotificationData;
}
export interface WidgetListItem {
@@ -474,6 +596,36 @@ export interface TrackerCompany {
displayName: string;
count: number;
}
+/**
+ * Generated from @see "../messages/customizer_onBackgroundUpdate.subscribe.json"
+ */
+export interface CustomizerOnBackgroundUpdateSubscription {
+ subscriptionEvent: "customizer_onBackgroundUpdate";
+ params: CustomizerOnBackgroundUpdateSubscribe;
+}
+export interface CustomizerOnBackgroundUpdateSubscribe {
+ background: BackgroundVariant;
+}
+/**
+ * Generated from @see "../messages/customizer_onImagesUpdate.subscribe.json"
+ */
+export interface CustomizerOnImagesUpdateSubscription {
+ subscriptionEvent: "customizer_onImagesUpdate";
+ params: CustomizerOnImagesUpdateSubscribe;
+}
+export interface CustomizerOnImagesUpdateSubscribe {
+ userImages: UserImage[];
+}
+/**
+ * Generated from @see "../messages/customizer_onThemeUpdate.subscribe.json"
+ */
+export interface CustomizerOnThemeUpdateSubscription {
+ subscriptionEvent: "customizer_onThemeUpdate";
+ params: CustomizerOnThemeUpdateSubscribe;
+}
+export interface CustomizerOnThemeUpdateSubscribe {
+ theme: BrowserTheme;
+}
/**
* Generated from @see "../messages/favorites_onConfigUpdate.subscribe.json"
*/