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

@ -1,2 +1,5 @@
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.'cfg(windows)']
rustflags = ["-C", "link-args=/STACK:8388608"]

257
Cargo.lock generated
View File

@ -135,6 +135,24 @@ version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arboard"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89"
dependencies = [
"clipboard-win",
"core-graphics",
"image 0.25.1",
"log",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"parking_lot 0.12.3",
"windows-sys 0.48.0",
"x11rb",
]
[[package]]
name = "arrayref"
version = "0.3.7"
@ -448,6 +466,12 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitcoin"
version = "0.29.2"
@ -921,6 +945,15 @@ dependencies = [
"vec_map",
]
[[package]]
name = "clipboard-win"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"error-code",
]
[[package]]
name = "cocoa"
version = "0.25.0"
@ -951,6 +984,12 @@ dependencies = [
"objc",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colored"
version = "2.1.0"
@ -1121,6 +1160,16 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
@ -1647,12 +1696,34 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "error-code"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "exr"
version = "1.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
dependencies = [
"bit_field",
"flume 0.11.0",
"half 2.4.1",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "fastrand"
version = "2.1.0"
@ -1731,6 +1802,15 @@ dependencies = [
"spin 0.9.8",
]
[[package]]
name = "flume"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [
"spin 0.9.8",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -2055,6 +2135,16 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac6c41a39c60ae1fc5bf0e220347ce90fa1e4bb0fcdac65b09bb5f4576bebc84"
[[package]]
name = "gethostname"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
dependencies = [
"libc",
"windows-targets 0.48.5",
]
[[package]]
name = "getrandom"
version = "0.1.16"
@ -2087,6 +2177,16 @@ dependencies = [
"polyval",
]
[[package]]
name = "gif"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "gimli"
version = "0.29.0"
@ -2311,6 +2411,16 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -2690,6 +2800,24 @@ dependencies = [
"winapi",
]
[[package]]
name = "image"
version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"num-traits",
"png",
"qoi",
"tiff",
]
[[package]]
name = "image"
version = "0.25.1"
@ -2699,6 +2827,8 @@ dependencies = [
"bytemuck",
"byteorder",
"num-traits",
"png",
"tiff",
]
[[package]]
@ -2852,6 +2982,15 @@ dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
version = "0.3.69"
@ -3063,6 +3202,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libappindicator"
version = "0.9.0"
@ -4768,13 +4913,22 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "qrcode"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
dependencies = [
"image",
"image 0.25.1",
]
[[package]]
@ -4967,6 +5121,26 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -5715,7 +5889,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"half 1.8.3",
"serde",
]
@ -6207,7 +6381,7 @@ dependencies = [
"dotenvy",
"either",
"event-listener",
"flume",
"flume 0.10.14",
"futures-channel",
"futures-core",
"futures-executor",
@ -6740,6 +6914,39 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-plugin"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51a5c65ab8536a7e27b70ecbb0713ab42e8508acd9af1bc4a0817ccf7caf3165"
dependencies = [
"anyhow",
"glob",
"plist",
"schemars",
"serde",
"serde_json",
"tauri-utils",
"toml 0.8.2",
"walkdir",
]
[[package]]
name = "tauri-plugin-clipboard-manager"
version = "2.1.0-beta.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "becbc5a692e842f8d6a7ab5e490c3c36d267b5c3d5bf4b6a0cdd039d7df25569"
dependencies = [
"arboard",
"image 0.24.9",
"log",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
]
[[package]]
name = "tauri-runtime"
version = "2.0.0-rc.1"
@ -6914,6 +7121,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tiff"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "time"
version = "0.1.45"
@ -7659,6 +7877,7 @@ dependencies = [
"swap",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
]
[[package]]
@ -8057,6 +8276,12 @@ dependencies = [
"windows-core 0.57.0",
]
[[package]]
name = "weezl"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "which"
version = "4.4.2"
@ -8504,6 +8729,23 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "x11rb"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12"
dependencies = [
"gethostname",
"rustix",
"x11rb-protocol",
]
[[package]]
name = "x11rb-protocol"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
[[package]]
name = "x25519-dalek"
version = "1.1.1"
@ -8593,3 +8835,12 @@ dependencies = [
"thiserror",
"time 0.1.45",
]
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]

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"

View File

@ -21,3 +21,4 @@ serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
swap = { path = "../swap", features = [ "tauri" ] }
tauri = { version = "2.0.0-rc.1", features = [ "config-json5" ] }
tauri-plugin-clipboard-manager = "2.1.0-beta.7"

View File

@ -2,6 +2,12 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": ["core:event:allow-emit", "core:event:default"]
}
"windows": [
"main"
],
"permissions": [
"core:event:allow-emit",
"core:event:default",
"clipboard-manager:allow-write-text"
]
}

View File

@ -112,6 +112,7 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.invoke_handler(tauri::generate_handler![
get_balance,
get_swap_infos_all,