import { Table, TableBody, TableCell, TableContainer, TableRow, Typography, IconButton, Box, Tooltip, Select, MenuItem, TableHead, Paper, Button, Dialog, DialogContent, DialogActions, DialogTitle, useTheme, Switch, SelectChangeEvent, } from "@mui/material"; import { removeNode, resetSettings, setFetchFiatPrices, setFiatCurrency, } from "store/features/settingsSlice"; import { addNode, Blockchain, FiatCurrency, moveUpNode, Network, setTheme, } from "store/features/settingsSlice"; import { useAppDispatch, useAppSelector, useNodes, useSettings, } from "store/hooks"; import ValidatedTextField from "renderer/components/other/ValidatedTextField"; import HelpIcon from "@mui/icons-material/HelpOutline"; import { ReactNode, useState } from "react"; import { Theme } from "renderer/components/theme"; import { Add, ArrowUpward, Delete, Edit, HourglassEmpty, } from "@mui/icons-material"; import { getNetwork } from "store/config"; import { currencySymbol } from "utils/formatUtils"; import { setTorEnabled } from "store/features/settingsSlice"; import InfoBox from "renderer/components/modal/swap/InfoBox"; const PLACEHOLDER_ELECTRUM_RPC_URL = "ssl://blockstream.info:700"; const PLACEHOLDER_MONERO_NODE_URL = "http://xmr-node.cakewallet.com:18081"; /** * The settings box, containing the settings for the GUI. */ export default function SettingsBox() { const theme = useTheme(); return ( Settings } mainContent={ Customize the settings of the GUI. Some of these require a restart to take effect. } additionalContent={ <> {/* Table containing the settings */}
{/* Reset button with a bit of spacing */} ({ mt: theme.spacing(2), })} /> } icon={null} loading={false} /> ); } /** * A button that allows you to reset the settings. * Opens a modal that asks for confirmation first. */ function ResetButton() { const dispatch = useAppDispatch(); const [modalOpen, setModalOpen] = useState(false); const onReset = () => { dispatch(resetSettings()); setModalOpen(false); }; return ( <> setModalOpen(false)}> Reset Settings Are you sure you want to reset the settings? ); } /** * A setting that allows you to enable or disable the fetching of fiat prices. */ function FetchFiatPricesSetting() { const fetchFiatPrices = useSettings((s) => s.fetchFiatPrices); const dispatch = useAppDispatch(); return ( <> dispatch(setFetchFiatPrices(event.currentTarget.checked)) } /> {fetchFiatPrices ? : <>} ); } /** * A setting that allows you to select the fiat currency to display prices in. */ function FiatCurrencySetting() { const fiatCurrency = useSettings((s) => s.fiatCurrency); const dispatch = useAppDispatch(); const onChange = (e: SelectChangeEvent) => dispatch(setFiatCurrency(e.target.value as FiatCurrency)); return ( ); } /** * URL validation function, forces the URL to be in the format of "protocol://host:port/" */ function isValidUrl(url: string, allowedProtocols: string[]): boolean { const urlPattern = new RegExp( `^(${allowedProtocols.join("|")})://[^\\s]+:\\d+/?$`, ); return urlPattern.test(url); } /** * A setting that allows you to select the Electrum RPC URL to use. */ function ElectrumRpcUrlSetting() { const [tableVisible, setTableVisible] = useState(false); const network = getNetwork(); const isValid = (url: string) => isValidUrl(url, ["ssl", "tcp"]); return ( setTableVisible(true)} size="large"> {} {tableVisible ? ( setTableVisible(false)} network={network} blockchain={Blockchain.Bitcoin} isValid={isValid} placeholder={PLACEHOLDER_ELECTRUM_RPC_URL} /> ) : ( <> )} ); } /** * A label for a setting, with a tooltip icon. */ function SettingLabel({ label, tooltip, }: { label: ReactNode; tooltip: string | null; }) { return ( {label} ); } /** * A setting that allows you to select the Monero Node URL to use. */ function MoneroNodeUrlSetting() { const network = getNetwork(); const [tableVisible, setTableVisible] = useState(false); const isValid = (url: string) => isValidUrl(url, ["http"]); return ( setTableVisible(!tableVisible)} size="large"> {tableVisible ? ( setTableVisible(false)} network={network} blockchain={Blockchain.Monero} isValid={isValid} placeholder={PLACEHOLDER_MONERO_NODE_URL} /> ) : ( <> )} ); } /** * A setting that allows you to select the theme of the GUI. */ function ThemeSetting() { const theme = useAppSelector((s) => s.settings.theme); const dispatch = useAppDispatch(); return ( ); } /** * A modal containing a NodeTable for a given network and blockchain. * It allows you to add, remove, and move nodes up the list. */ function NodeTableModal({ open, onClose, network, isValid, placeholder, blockchain, }: { network: Network; blockchain: Blockchain; isValid: (url: string) => boolean; placeholder: string; open: boolean; onClose: () => void; }) { return ( Available Nodes When the daemon is started, it will attempt to connect to the first available {blockchain} node in this list. If you leave this field empty or all nodes are unavailable, it will choose from a list of known nodes at random. Requires a restart to take effect. ); } // Create a circle SVG with a given color and radius function Circle({ color, radius = 6 }: { color: string; radius?: number }) { return ( ); } /** * Displays a status indicator for a node */ function NodeStatus({ status }: { status: boolean | undefined }) { const theme = useTheme(); switch (status) { case true: return ( ); case false: return ( ); default: return ( ); } } /** * A table that displays the available nodes for a given network and blockchain. * It allows you to add, remove, and move nodes up the list. * It fetches the nodes from the store (nodesSlice) and the statuses of all nodes every 15 seconds. */ function NodeTable({ network, blockchain, isValid, placeholder, }: { network: Network; blockchain: Blockchain; isValid: (url: string) => boolean; placeholder: string; }) { const availableNodes = useSettings((s) => s.nodes[network][blockchain]); const currentNode = availableNodes[0]; const nodeStatuses = useNodes((s) => s.nodes); const [newNode, setNewNode] = useState(""); const dispatch = useAppDispatch(); const onAddNewNode = () => { dispatch(addNode({ network, type: blockchain, node: newNode })); setNewNode(""); }; const onRemoveNode = (node: string) => dispatch(removeNode({ network, type: blockchain, node })); const onMoveUpNode = (node: string) => dispatch(moveUpNode({ network, type: blockchain, node })); const moveUpButton = (node: string) => { if (currentNode === node) return <>; return ( onMoveUpNode(node)} size="large"> ); }; return ( {/* Table header row */} Node URL Status Actions {/* Table body rows: one for each node */} {availableNodes.map((node, index) => ( {/* Node URL */} {node} {/* Node status icon */} {/* Remove and move buttons */} onRemoveNode(node)} children={} size="large" /> } /> {moveUpButton(node)} ))} {/* Last row: add a new node */}
); } export function TorSettings() { const dispatch = useAppDispatch(); const torEnabled = useSettings((settings) => settings.enableTor); const handleChange = (event: React.ChangeEvent) => dispatch(setTorEnabled(event.target.checked)); const status = (state: boolean) => (state === true ? "enabled" : "disabled"); return ( ); }