feat(gui): Clickable addresses to copy to clipboard (#38)

This commit is contained in:
Einliterflasche 2024-08-29 14:28:23 +02:00 committed by GitHub
parent 1b1fe0add5
commit ff1ded55ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 405 additions and 37 deletions

View file

@ -15,6 +15,7 @@
"@material-ui/lab": "^4.0.0-alpha.61",
"@reduxjs/toolkit": "^2.2.6",
"@tauri-apps/api": "2.0.0-rc.1",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.0",
"humanize-duration": "^3.32.1",
"lodash": "^4.17.21",
"multiaddr": "^10.0.1",

View file

@ -34,6 +34,11 @@ const theme = createTheme({
disableRipple: true,
},
},
typography: {
overline: {
textTransform: 'none', // This prevents the text from being all caps
},
}
});
function InnerContent() {

View file

@ -1,8 +1,7 @@
import { Box, Typography } from "@material-ui/core";
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
import { Box } from "@material-ui/core";
import { ReactNode } from "react";
import CopyableMonospaceTextBox from "renderer/components/other/CopyableMonospaceTextBox";
import BitcoinQrCode from "./BitcoinQrCode";
import ClipboardIconButton from "./ClipbiardIconButton";
import InfoBox from "./InfoBox";
type Props = {
@ -21,29 +20,18 @@ export default function DepositAddressInfoBox({
return (
<InfoBox
title={title}
mainContent={<Typography variant="h5">{address}</Typography>}
mainContent={<CopyableMonospaceTextBox address={address} />}
additionalContent={
<Box>
<Box>
<ClipboardIconButton
text={address}
endIcon={<FileCopyOutlinedIcon />}
color="primary"
variant="contained"
size="medium"
/>
<Box
style={{
display: "flex",
flexDirection: "row",
gap: "0.5rem",
alignItems: "center",
}}
>
<Box>{additionalContent}</Box>
<BitcoinQrCode address={address} />
</Box>
</Box>
<Box
style={{
display: "flex",
flexDirection: "row",
gap: "0.5rem",
alignItems: "center",
}}
>
<Box>{additionalContent}</Box>
<BitcoinQrCode address={address} />
</Box>
}
icon={icon}

View file

@ -26,7 +26,7 @@ const useStyles = makeStyles((theme) => ({
upperContent: {
display: "flex",
alignItems: "center",
gap: theme.spacing(0.5),
gap: theme.spacing(1),
},
}));

View file

@ -0,0 +1,46 @@
import { Box, Tooltip } from "@material-ui/core";
import { FileCopyOutlined } from "@material-ui/icons";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useState } from "react";
import MonospaceTextBox from "./MonospaceTextBox";
type Props = {
address: string;
noIcon?: boolean;
};
/** Display addresses monospaced and clickable such that a click copies the address to the clipboard. */
export default function CopyableMonospaceTextBox({
address,
noIcon = false,
}: Props) {
// Signal that the address was copied
const [copied, setCopied] = useState(false);
const tooltip = copied ? "Copied to clipboard" : "Click to copy";
// Copy address to clipboard on-click
const handleClick = async () => {
// Copy to clipboard
await writeText(address);
// Change tooltip to show that we copied the address
setCopied(true);
// After a delay, show default tooltip again (2sec)
setTimeout(() => setCopied(false), 2_000);
};
// Apply icon unless specified otherwise
const icon = noIcon ? null : <FileCopyOutlined />;
return (
<Tooltip title={tooltip} arrow>
{/* Div is necessary to make the tooltip work */}
<Box style={{ cursor: "pointer" }}>
<MonospaceTextBox
content={address}
endIcon={icon}
onClick={handleClick}
/>
</Box>
</Tooltip>
);
}

View file

@ -0,0 +1,44 @@
import { Box, Typography, makeStyles } from "@material-ui/core";
import { ReactNode } from "react";
type Props = {
content: string;
onClick?: (content: string) => void;
endIcon?: ReactNode;
};
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
alignItems: "center",
backgroundColor: theme.palette.grey[900],
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(1),
gap: theme.spacing(1),
},
content: {
wordBreak: "break-word",
whiteSpace: "pre-wrap",
fontFamily: "monospace",
lineHeight: "1.5em",
},
}));
export default function MonospaceTextBox({ content, endIcon, onClick }: Props) {
const classes = useStyles();
const handleClick = () => onClick?.(content);
return (
<Box className={classes.root} onClick={handleClick}>
<Typography
component="span"
variant="overline"
className={classes.content}
>
{content}
</Typography>
{endIcon}
</Box>
);
}

View file

@ -61,7 +61,7 @@ export function SwapCancelRefundButton({
export default function HistoryRowActions(swap: GetSwapInfoResponse) {
if (swap.state_name === BobStateName.XmrRedeemed) {
return (
<Tooltip title="The swap is completed because you have redeemed the XMR">
<Tooltip title="This swap is completed. You have redeemed the Monero.">
<DoneIcon style={{ color: green[500] }} />
</Tooltip>
);
@ -69,7 +69,7 @@ export default function HistoryRowActions(swap: GetSwapInfoResponse) {
if (swap.state_name === BobStateName.BtcRefunded) {
return (
<Tooltip title="The swap is completed because your BTC have been refunded">
<Tooltip title="This swap is completed. Your Bitcoin has been refunded.">
<DoneIcon style={{ color: green[500] }} />
</Tooltip>
);
@ -79,7 +79,7 @@ export default function HistoryRowActions(swap: GetSwapInfoResponse) {
// See this PR: https://github.com/UnstoppableSwap/unstoppableswap-gui/pull/212
if (swap.state_name === BobStateName.BtcPunished) {
return (
<Tooltip title="The swap is completed because you have been punished">
<Tooltip title="This swap is completed. You have been punished.">
<ErrorIcon style={{ color: red[500] }} />
</Tooltip>
);

View file

@ -8,7 +8,10 @@ import {
TableContainer,
TableRow,
} from "@material-ui/core";
import { OpenInNew } from "@material-ui/icons";
import { GetSwapInfoResponse } from "models/tauriModel";
import CopyableMonospaceTextBox from "renderer/components/other/CopyableAddress";
import MonospaceTextBox from "renderer/components/other/InlineCode";
import {
MoneroBitcoinExchangeRate,
PiconeroAmount,
@ -85,7 +88,11 @@ export default function HistoryRowExpanded({
<TableRow>
<TableCell>Provider Address</TableCell>
<TableCell>
<Box>{swap.seller.addresses.join(", ")}</Box>
<Box>
{swap.seller.addresses.map((addr) => (
<CopyableMonospaceTextBox key={addr} address={addr} />
))}
</Box>
</TableCell>
</TableRow>
<TableRow>
@ -95,7 +102,10 @@ export default function HistoryRowExpanded({
href={getBitcoinTxExplorerUrl(swap.tx_lock_id, isTestnet())}
target="_blank"
>
{swap.tx_lock_id}
<MonospaceTextBox
content={swap.tx_lock_id}
endIcon={<OpenInNew />}
/>
</Link>
</TableCell>
</TableRow>

View file

@ -630,6 +630,11 @@
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.1.tgz#ec858f239e34792625e311f687fcaca0581e0904"
integrity sha512-qubAWjM9sqofUh7fe+7UAbBY3wlkfCyxm+PNRYpq9mnNng7lvSQq3sYsFUEB12AYvgGARZSb54VMVUvRuVLi7w==
"@tauri-apps/api@^2.0.0-rc.0":
version "2.0.0-rc.3"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.0.0-rc.3.tgz#1dd17530de9cafd854f77d3feeca1732a985a81e"
integrity sha512-k1erUfnoOFJwL5VNFZz0BQZ2agNstG7CNOjwpdWMl1vOaVuSn4DhJtXB0Deh9lZaaDlfrykKOyZs9c3XXpMi5Q==
"@tauri-apps/cli-darwin-arm64@2.0.0-beta.21":
version "2.0.0-beta.21"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.21.tgz#9dc6f306b14d58b0b4fbf218ffbb31831e28cf4d"
@ -696,6 +701,13 @@
"@tauri-apps/cli-win32-ia32-msvc" "2.0.0-beta.21"
"@tauri-apps/cli-win32-x64-msvc" "2.0.0-beta.21"
"@tauri-apps/plugin-clipboard-manager@^2.0.0-rc.0":
version "2.0.0-rc.0"
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.0.tgz#8371fa2a2092c67d0cfd9322698c14115735459e"
integrity sha512-2fS3wbRQEtorkk3Np2msJUeKCXRqLQ9sSo2FzlFdUPYNzThsu43uWCF55McGLAfltNOvXQIcQLUBf05jbBL/5w==
dependencies:
"@tauri-apps/api" "^2.0.0-rc.0"
"@types/babel__core@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"