Skip to content

Commit 50cf08b

Browse files
authored
ntp: support right-click on images (#1403)
* ntp: support right-click on images * package-lock.json
1 parent 399ce49 commit 50cf08b

File tree

12 files changed

+127
-14
lines changed

12 files changed

+127
-14
lines changed

package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

special-pages/pages/new-tab/app/customizer/CustomizerProvider.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { createContext, h } from 'preact';
22
import { useCallback } from 'preact/hooks';
3-
import { effect, signal, useSignal } from '@preact/signals';
3+
import { signal, useSignal, useSignalEffect } from '@preact/signals';
44
import { useThemes } from './themes.js';
55

66
/**
77
* @typedef {import('../../types/new-tab.js').CustomizerData} CustomizerData
88
* @typedef {import('../../types/new-tab.js').BackgroundData} BackgroundData
99
* @typedef {import('../../types/new-tab.js').ThemeData} ThemeData
1010
* @typedef {import('../../types/new-tab.js').UserImageData} UserImageData
11+
* @typedef {import('../../types/new-tab.js').UserImageContextMenu} UserImageContextMenu
1112
* @typedef {import('../service.hooks.js').State<CustomizerData, undefined>} State
1213
* @typedef {import('../service.hooks.js').Events<CustomizerData, undefined>} Events
1314
*/
@@ -41,6 +42,10 @@ export const CustomizerContext = createContext({
4142
* @type {(id: string) => void}
4243
*/
4344
deleteImage: (id) => {},
45+
/**
46+
* @param {UserImageContextMenu} params
47+
*/
48+
customizerContextMenu: (params) => {},
4449
});
4550

4651
/**
@@ -57,7 +62,7 @@ export function CustomizerProvider({ service, initialData, children }) {
5762
const data = useSignal(initialData);
5863
const { main, browser } = useThemes(data);
5964

60-
effect(() => {
65+
useSignalEffect(() => {
6166
const unsub = service.onBackground((evt) => {
6267
data.value = { ...data.value, background: evt.data.background };
6368
});
@@ -105,8 +110,11 @@ export function CustomizerProvider({ service, initialData, children }) {
105110
[service],
106111
);
107112

113+
/** @type {(p: UserImageContextMenu) => void} */
114+
const customizerContextMenu = useCallback((params) => service.contextMenu(params), [service]);
115+
108116
return (
109-
<CustomizerContext.Provider value={{ data, select, upload, setTheme, deleteImage }}>
117+
<CustomizerContext.Provider value={{ data, select, upload, setTheme, deleteImage, customizerContextMenu }}>
110118
<CustomizerThemesContext.Provider value={{ main, browser }}>{children}</CustomizerThemesContext.Provider>
111119
</CustomizerContext.Provider>
112120
);

special-pages/pages/new-tab/app/customizer/components/Customizer.examples.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const customizerExamples = {
5555
back={noop('back')}
5656
onUpload={noop('onUpload')}
5757
deleteImage={noop('deleteImage')}
58+
customizerContextMenu={noop('customizerContextMenu')}
5859
/>
5960
);
6061
}}

special-pages/pages/new-tab/app/customizer/components/CustomizerDrawer.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ export function CustomizerDrawer({ displayChildren }) {
1313
}
1414

1515
function CustomizerConsumer() {
16-
const { data, select, upload, setTheme, deleteImage } = useContext(CustomizerContext);
17-
return <CustomizerDrawerInner data={data} select={select} onUpload={upload} setTheme={setTheme} deleteImage={deleteImage} />;
16+
const { data, select, upload, setTheme, deleteImage, customizerContextMenu } = useContext(CustomizerContext);
17+
return (
18+
<CustomizerDrawerInner
19+
data={data}
20+
select={select}
21+
onUpload={upload}
22+
setTheme={setTheme}
23+
deleteImage={deleteImage}
24+
customizerContextMenu={customizerContextMenu}
25+
/>
26+
);
1827
}

special-pages/pages/new-tab/app/customizer/components/CustomizerDrawerInner.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { InlineErrorBoundary } from '../../InlineErrorBoundary.js';
1616
import { useTypedTranslationWith } from '../../types.js';
1717

1818
/**
19-
* @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, BackgroundData } from '../../../types/new-tab.js'
19+
* @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, BackgroundData, UserImageContextMenu } from '../../../types/new-tab.js'
2020
* @import enStrings from '../strings.json';
2121
*/
2222

@@ -27,8 +27,9 @@ import { useTypedTranslationWith } from '../../types.js';
2727
* @param {() => void} props.onUpload
2828
* @param {(theme: import('../../../types/new-tab').ThemeData) => void} props.setTheme
2929
* @param {(id: string) => void} props.deleteImage
30+
* @param {(p: UserImageContextMenu) => void} props.customizerContextMenu
3031
*/
31-
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage }) {
32+
export function CustomizerDrawerInner({ data, select, onUpload, setTheme, deleteImage, customizerContextMenu }) {
3233
const { close } = useDrawerControls();
3334
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
3435
return (
@@ -73,7 +74,14 @@ export function CustomizerDrawerInner({ data, select, onUpload, setTheme, delete
7374
{id === 'color' && <ColorSelection data={data} select={select} back={pop} />}
7475
{id === 'gradient' && <GradientSelection data={data} select={select} back={pop} />}
7576
{id === 'image' && (
76-
<ImageSelection data={data} select={select} back={pop} onUpload={onUpload} deleteImage={deleteImage} />
77+
<ImageSelection
78+
data={data}
79+
select={select}
80+
back={pop}
81+
onUpload={onUpload}
82+
deleteImage={deleteImage}
83+
customizerContextMenu={customizerContextMenu}
84+
/>
7785
)}
7886
</Fragment>
7987
)}

special-pages/pages/new-tab/app/customizer/components/ImageSelection.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useTypedTranslationWith } from '../../types.js';
1212

1313
/**
1414
* @import enStrings from '../strings.json';
15-
* @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, BackgroundData, PredefinedGradient } from '../../../types/new-tab.js'
15+
* @import { Widgets, WidgetConfigItem, WidgetVisibility, VisibilityMenuItem, CustomizerData, BackgroundData, PredefinedGradient, UserImageContextMenu } from '../../../types/new-tab.js'
1616
*/
1717

1818
/**
@@ -22,8 +22,9 @@ import { useTypedTranslationWith } from '../../types.js';
2222
* @param {() => void} props.back
2323
* @param {() => void} props.onUpload
2424
* @param {(id: string) => void} props.deleteImage
25+
* @param {(p: UserImageContextMenu) => void} props.customizerContextMenu
2526
*/
26-
export function ImageSelection({ data, select, back, onUpload, deleteImage }) {
27+
export function ImageSelection({ data, select, back, onUpload, deleteImage, customizerContextMenu }) {
2728
const { t } = useTypedTranslationWith(/** @type {enStrings} */ ({}));
2829
function onClick(event) {
2930
let target = /** @type {HTMLElement|null} */ (event.target);
@@ -38,8 +39,19 @@ export function ImageSelection({ data, select, back, onUpload, deleteImage }) {
3839
select({ background: { kind: 'userImage', value: match } });
3940
}
4041

42+
function onContextMenu(event) {
43+
const target = /** @type {HTMLElement|null} */ (event.target);
44+
if (!(target instanceof HTMLElement)) return;
45+
const id = target.closest('button')?.dataset.id;
46+
if (typeof id === 'string') {
47+
event.preventDefault();
48+
event.stopImmediatePropagation();
49+
customizerContextMenu({ id, target: 'userImage' });
50+
}
51+
}
52+
4153
return (
42-
<div>
54+
<div onContextMenu={onContextMenu}>
4355
<button type={'button'} onClick={back} class={cn(styles.backBtn, styles.sectionTitle)}>
4456
<BackChevron />
4557
{t('customizer_background_selection_image_existing')}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,14 @@ title: Customizer
161161
"id": "abc"
162162
}
163163
```
164+
165+
- {@link "NewTab Messages".CustomizerContextMenuNotification `customizer_contextMenu`}.
166+
- Sends {@link "NewTab Messages".UserImageContextMenu}
167+
- Note: only sent for right-clicks on user images in the selection screen.
168+
- For example:
169+
- ```json
170+
{
171+
"target": "userImage",
172+
"id": "01"
173+
}
174+
```

special-pages/pages/new-tab/app/customizer/customizer.service.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* @typedef {import("../../types/new-tab.js").CustomizerData} CustomizerData
33
* @typedef {import("../../types/new-tab.js").UserImageData} UserImageData
44
* @typedef {import("../../types/new-tab.js").UserColorData} UserColorData
5+
* @typedef {import("../../types/new-tab.js").UserImageContextMenu} UserImageContextMenu
56
* @typedef {import("../../types/new-tab.js").ThemeData} ThemeData
67
* @typedef {import("../../types/new-tab.js").BackgroundData} BackgroundData
78
*/
@@ -135,4 +136,11 @@ export class CustomizerService {
135136
});
136137
this.ntp.messaging.notify('customizer_setTheme', theme);
137138
}
139+
140+
/**
141+
* @param {import('../../types/new-tab.js').UserImageContextMenu} params
142+
*/
143+
contextMenu(params) {
144+
this.ntp.messaging.notify('customizer_contextMenu', params);
145+
}
138146
}

special-pages/pages/new-tab/app/customizer/integration-tests/customizer.page.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,4 +441,11 @@ export class CustomizerPage {
441441
{ value: 'gradient07' },
442442
]);
443443
}
444+
445+
async rightClicksFirstImage() {
446+
const { page } = this.ntp;
447+
await page.getByRole('radio', { name: 'Select image 1' }).click({
448+
button: 'right',
449+
});
450+
}
444451
}

special-pages/pages/new-tab/app/customizer/integration-tests/customizer.spec.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { test } from '@playwright/test';
1+
import { test, expect } from '@playwright/test';
22
import { NewtabPage } from '../../../integration-tests/new-tab.page.js';
33
import { CustomizerPage } from './customizer.page.js';
44

@@ -265,4 +265,23 @@ test.describe('newtab customizer', () => {
265265
await cp.opensCustomizer();
266266
await cp.opensSettings();
267267
});
268+
test('context menu for image selections', async ({ page }, workerInfo) => {
269+
const ntp = NewtabPage.create(page, workerInfo);
270+
await ntp.reducedMotion();
271+
const cp = new CustomizerPage(ntp);
272+
await ntp.openPage({ additional: { customizerDrawer: 'enabled', userImages: 'true' } });
273+
await cp.opensCustomizer();
274+
await cp.opensImages();
275+
await cp.rightClicksFirstImage();
276+
const calls = await ntp.mocks.waitForCallCount({ method: 'customizer_contextMenu', count: 1 });
277+
expect(calls[0].payload).toStrictEqual({
278+
context: 'specialPages',
279+
featureName: 'newTabPage',
280+
method: 'customizer_contextMenu',
281+
params: {
282+
target: 'userImage',
283+
id: '01',
284+
},
285+
});
286+
});
268287
});

0 commit comments

Comments
 (0)