Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed a mouse clicking issue #2502

Merged
merged 2 commits into from
Oct 1, 2024
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
57 changes: 45 additions & 12 deletions packages/core/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { alpha, Button as BaseButton, ButtonProps as BaseButtonProps } from '@mui/material';
import React from 'react';
import React, { SyntheticEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';

Expand Down Expand Up @@ -53,24 +53,57 @@ export default function Button(props: ButtonProps) {

const navigate = useNavigate();

function handleClick(...args) {
if (to) {
navigate(to);
}
const handleClick = React.useCallback(
(...args: any[]) => {
if (to) {
navigate(to);
}

if (onClick) {
onClick(...args);
}
},
[to, navigate, onClick],
);

if (onClick) {
onClick(...args);
const onAuxClick = React.useMemo(() => {
if (!rest.href) {
return undefined;
}
}
return (event: SyntheticEvent, ...restArgs: any[]) => {
event.preventDefault();
handleClick(...restArgs);
};
}, [rest.href, handleClick]);

switch (color) {
case 'danger':
return <DangerButton onClick={handleClick} disableElevation={disableElevation} {...rest} />;
return (
<DangerButton {...rest} onClick={handleClick} onAuxClick={onAuxClick} disableElevation={disableElevation} />
);
case 'primary':
return <StyledBaseButton onClick={handleClick} disableElevation={disableElevation} color="primary" {...rest} />;
return (
<StyledBaseButton
{...rest}
onClick={handleClick}
onAuxClick={onAuxClick}
disableElevation={disableElevation}
color="primary"
/>
);
case 'secondary':
return <StyledBaseButton onClick={handleClick} disableElevation={disableElevation} color="secondary" {...rest} />;
return (
<StyledBaseButton
{...rest}
onClick={handleClick}
onAuxClick={onAuxClick}
disableElevation={disableElevation}
color="secondary"
/>
);
default:
return <StyledBaseButton onClick={handleClick} disableElevation={disableElevation} {...rest} />;
return (
<StyledBaseButton {...rest} onClick={handleClick} onAuxClick={onAuxClick} disableElevation={disableElevation} />
);
}
}
14 changes: 11 additions & 3 deletions packages/core/src/components/Link/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,27 @@ export default function Link(props: Props) {
};

function handleClick(event: SyntheticEvent) {
event.preventDefault();

if (onClick) {
event.preventDefault();
event.stopPropagation();
onClick(event);
return;
}

if (href && target === '_blank') {
event.preventDefault();
event.stopPropagation();
openExternal(href);
}
}

return <StyledBaseLink component={to ? RouterLink : BaseLink} {...newProps} onClick={handleClick} />;
// `onAuxClick` is used to handle middle mouse click as well as other mouse buttons.
return (
<StyledBaseLink
component={to ? RouterLink : BaseLink}
{...newProps}
onClick={handleClick}
onAuxClick={handleClick}
/>
);
}
39 changes: 26 additions & 13 deletions packages/core/src/components/MenuItem/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,34 @@ function MenuItem(props: MenuItemProps, ref: any) {

const menuContext: MenuContextInterface | undefined = useContext(MenuContext);

async function handleClick(event: React.MouseEvent<HTMLLIElement>) {
event.stopPropagation();

if (close === true) {
menuContext?.close(event, 'menuItemClick');
const handleClick = React.useCallback(
async (event: React.MouseEvent<HTMLLIElement>) => {
event.stopPropagation();

if (close === true) {
menuContext?.close(event, 'menuItemClick');
}

await onClick?.(event);

if (close === 'after') {
menuContext?.close(event, 'menuItemClick');
}
},
[close, menuContext, onClick],
);

const onAuxClick = React.useMemo(() => {
if (!rest.href) {
return undefined;
}
return (event: React.MouseEvent<HTMLLIElement>) => {
event.preventDefault();
return handleClick(event);
};
}, [rest.href, handleClick]);

await onClick?.(event);

if (close === 'after') {
menuContext?.close(event, 'menuItemClick');
}
}

const item = <BaseMenuItem {...rest} onClick={handleClick} ref={ref} />;
const item = <BaseMenuItem {...rest} onClick={handleClick} onAuxClick={onAuxClick} ref={ref} />;

if (tooltip) {
return <Tooltip title={tooltip}>{item}</Tooltip>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Flex, Link, DialogActions } from '@chia-network/core';
import { Trans } from '@lingui/macro';
import { Button, Dialog, DialogContent, DialogTitle, Typography } from '@mui/material';
import React from 'react';
import isURL from 'validator/es/lib/isURL';

import useOpenUnsafeLink from '../../hooks/useOpenUnsafeLink';

Expand All @@ -15,17 +16,30 @@ export default function NotificationAnnouncementDialog(props: NotificationAnnoun

const openUnsafeLink = useOpenUnsafeLink();

function handleClose() {
const handleClose = React.useCallback(() => {
onClose?.(false);
}
}, [onClose]);

const message = 'message' in notification ? notification.message : undefined;
const url = 'url' in notification ? notification.url : undefined;
const url = 'url' in notification && typeof notification.url === 'string' ? notification.url : undefined;
const from = 'from' in notification ? notification.from : undefined;

async function handleURLClick() {
const handleURLClick = React.useCallback(() => {
if (!url) {
return;
}
openUnsafeLink(url);
}
}, [url, openUnsafeLink]);

const urlLabel = React.useMemo(() => {
if (!url) {
return undefined;
}
if (!isURL(url)) {
return <Trans>##### Redacted for security reason #####</Trans>;
}
return url;
}, [url]);

return (
<Dialog onClose={handleClose} maxWidth="sm" fullWidth open>
Expand Down Expand Up @@ -62,9 +76,7 @@ export default function NotificationAnnouncementDialog(props: NotificationAnnoun
<Trans>URL</Trans>
</Typography>
<Typography>
<Link href={url} target="_blank" onClick={handleURLClick}>
{url}
</Link>
<Link onClick={handleURLClick}>{urlLabel}</Link>
</Typography>
</Flex>
) : null}
Expand Down
20 changes: 12 additions & 8 deletions packages/gui/src/electron/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import React from 'react';
// import os from 'os';
import ReactDOMServer from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
import isURL from 'validator/es/lib/isURL';

// handle setupevents as quickly as possible
import '../config/env';
Expand All @@ -40,6 +41,16 @@ import { readAddressBook, saveAddressBook } from './addressBook';
import installDevTools from './installDevTools.dev';
import { readPrefs, savePrefs, migratePrefs } from './prefs';

/**
* Open the given external protocol URL in the desktop’s default manner.
*/
function openExternal(urlLocal: string) {
if (!isURL(urlLocal, { protocols: ['http', 'https', 'ipfs'], require_protocol: true })) {
return;
}
shell.openExternal(urlLocal);
}

const isPlaywrightTesting = process.env.PLAYWRIGHT_TESTS === 'true';
const NET = 'mainnet';

Expand Down Expand Up @@ -111,7 +122,7 @@ function openAbout() {
aboutWindow.loadURL(`data:text/html;charset=utf-8,${about}`);

aboutWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
openExternal(details.url);
return { action: 'deny' };
});

Expand Down Expand Up @@ -910,10 +921,3 @@ function getMenuTemplate() {

return template;
}

/**
* Open the given external protocol URL in the desktop’s default manner.
*/
function openExternal(urlLocal) {
shell.openExternal(urlLocal);
}
40 changes: 39 additions & 1 deletion packages/gui/src/hooks/useOpenUnsafeLink.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
import { usePrefs } from '@chia-network/api-react';
import { ConfirmDialog, CopyToClipboard, Flex, useOpenDialog, useOpenExternal } from '@chia-network/core';
import { AlertDialog, ConfirmDialog, CopyToClipboard, Flex, useOpenDialog, useOpenExternal } from '@chia-network/core';
import { Trans } from '@lingui/macro';
import { Checkbox, FormControlLabel, InputAdornment, TextField, Typography } from '@mui/material';
import React from 'react';
import isURL from 'validator/es/lib/isURL';

/* ========================================================================== */

const SuppressUnsafeLinkWarningLocalStorageKey = 'suppressUnsafeLinkWarning';

/* ========================================================================== */
type InvalidURLWarningDialogProps = {
url: string;
};

function InvalidURLWarningDialog(props: InvalidURLWarningDialogProps) {
const { url, ...rest } = props;

return (
<AlertDialog {...rest} title={<Trans>Warning: Invalid URL</Trans>}>
<Flex flexDirection="column" gap={2}>
<Typography>
<Trans>This type of the URL is not allowed to open for security reasons.</Trans>
</Typography>
<TextField
label={<Trans>URL</Trans>}
value={url}
variant="filled"
InputProps={{
readOnly: true,
}}
fullWidth
/>
</Flex>
</AlertDialog>
);
}

/* ========================================================================== */

type OpenUnsafeLinkConfirmationDialogProps = {
Expand Down Expand Up @@ -78,6 +107,15 @@ export default function useOpenUnsafeLink() {
async function openUnsafeLink(url: string) {
let openUrl = false;

if (!url) {
return;
}

if (!isURL(url)) {
await openDialog(<InvalidURLWarningDialog url={url} />);
return;
}

if (suppressUnsafeLinkWarning) {
openUrl = true;
} else {
Expand Down
Loading