@mui/material#Input TypeScript Examples
The following examples show how to use
@mui/material#Input.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Rename.tsx From your_spotify with GNU General Public License v3.0 | 6 votes |
export default function Rename() {
const dispatch = useAppDispatch();
const [name, setName] = useState('');
const submit = useCallback(
(ev: React.SyntheticEvent) => {
ev.preventDefault();
dispatch(changeUsername(name));
},
[name, dispatch],
);
return (
<form onSubmit={submit} className={s.root}>
<Input
placeholder="New name..."
fullWidth
value={name}
onChange={(ev) => setName(ev.target.value)}
/>
<Button type="submit" variant="contained">
Change
</Button>
</form>
);
}
Example #2
Source File: TextFilter.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 5 votes |
export default function TextFilter(props: Props) {
const {
state,
name,
position,
group,
updateFilterValue,
update,
} = props;
const [Search, setsearch] = React.useState(state || '');
let typingTimer: NodeJS.Timeout;
function doneTyping(e: React.ChangeEvent<HTMLInputElement>) {
const upd = update.filter((el: {
position: number; group: number | undefined;
}) => !(position === el.position && group === el.group));
updateFilterValue([...upd, { position, state: e.target.value, group }]);
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setsearch(e.target.value);
clearTimeout(typingTimer);
typingTimer = setTimeout(() => { doneTyping(e); }, 2500);
}
if (state !== undefined) {
return (
<Box key={`${name}`} sx={{ display: 'flex', flexDirection: 'row', minWidth: 120 }}>
<>
<SearchIcon
sx={{
margin: 'auto',
}}
/>
<FormControl fullWidth>
<InputLabel sx={{ margin: '10px 0 10px 0' }}>
{name}
</InputLabel>
<Input
name={name}
value={Search || ''}
onChange={handleChange}
sx={{ margin: '10px 0 10px 0' }}
/>
</FormControl>
</>
</Box>
);
}
return (<></>);
}
Example #3
Source File: AppbarSearch.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 5 votes |
AppbarSearch: React.FunctionComponent<IProps> = (props) => {
const { autoOpen } = props;
const [query, setQuery] = useQueryParam('query', StringParam);
const [searchOpen, setSearchOpen] = useState(!!query);
const inputRef = React.useRef<HTMLInputElement>();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setQuery(e.target.value === '' ? undefined : e.target.value);
}
const cancelSearch = () => {
setQuery(null);
setSearchOpen(false);
};
const handleBlur = () => { if (!query) setSearchOpen(false); };
const openSearch = () => {
setSearchOpen(true);
// Put Focus Action at the end of the Callstack so Input actually exists on the dom
setTimeout(() => {
if (inputRef && inputRef.current) inputRef.current.focus();
});
};
const handleSearchShortcut = (e: KeyboardEvent) => {
if ((e.code === 'F3') || (e.ctrlKey && e.code === 'KeyF')) {
e.preventDefault();
openSearch();
}
};
useEffect(() => {
if (autoOpen) {
openSearch();
}
}, []);
useEffect(() => {
window.addEventListener('keydown', handleSearchShortcut);
return () => {
window.removeEventListener('keydown', handleSearchShortcut);
};
}, [handleSearchShortcut]);
return (
<>
{searchOpen
? (
<Input
value={query || ''}
onChange={handleChange}
onBlur={handleBlur}
inputRef={inputRef}
endAdornment={(
<IconButton
onClick={cancelSearch}
>
<CancelIcon />
</IconButton>
)}
/>
) : (
<IconButton onClick={openSearch}>
<SearchIcon />
</IconButton>
)}
</>
);
}
Example #4
Source File: FormInput.tsx From frontend with MIT License | 5 votes |
export default function FormInput({ name, ...props }: FormInputProps) {
const [field] = useField(name)
return <Input {...field} {...props} />
}
Example #5
Source File: TableNumberInput.tsx From firecms with MIT License | 4 votes |
export function NumberTableInput(props: {
error: Error | undefined;
value: number;
align: "right" | "left" | "center";
updateValue: (newValue: (number | null)) => void;
focused: boolean;
disabled: boolean;
onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
}) {
const { align, value, updateValue, focused, onBlur, disabled } = props;
const propStringValue = (value && typeof value === "number") ? value.toString() : "";
const [internalValue, setInternalValue] = useState<string | null>(propStringValue);
useEffect(
() => {
const doUpdate = () => {
if (internalValue !== propStringValue) {
if (internalValue !== undefined && internalValue !== null) {
const numberValue = parseFloat(internalValue);
if (isNaN(numberValue))
return;
if (numberValue !== undefined && numberValue !== null)
updateValue(numberValue);
} else {
updateValue(null);
}
}
};
const handler = setTimeout(doUpdate, 300);
return () => {
clearTimeout(handler);
};
},
[internalValue]
);
useEffect(
() => {
if (!focused && propStringValue !== internalValue)
setInternalValue(value !== undefined && value !== null ? value.toString() : null);
},
[value, focused]
);
const ref = React.createRef<HTMLInputElement>();
const classes = useInputStyles();
useEffect(() => {
if (ref.current && focused) {
ref.current.focus({ preventScroll: true });
}
}, [focused, ref]);
const regexp = /^-?[0-9]+[,.]?[0-9]*$/;
return (
<Input
inputRef={ref}
style={{
width: "100%",
fontSize: "unset",
fontFamily: "unset",
background: "unset",
border: "unset",
resize: "none",
outline: "none",
padding: 0
}}
inputProps={{
style: {
textAlign: align
}
}}
disabled={disabled}
className={clsx(classes.input, classes.numberInput)}
disableUnderline
value={internalValue ?? ""}
onBlur={onBlur}
onChange={(evt) => {
const newValue = evt.target.value.replace(",", ".");
if (newValue.length === 0)
setInternalValue(null);
if (regexp.test(newValue) || newValue.startsWith("-"))
setInternalValue(newValue);
}}
/>
);
}
Example #6
Source File: DateTimeFilterfield.tsx From firecms with MIT License | 4 votes |
export function DateTimeFilterField({
name,
isArray,
value,
setValue,
title
}: DateTimeFilterFieldProps) {
const possibleOperations: (keyof typeof operationLabels) [] = isArray
? ["array-contains"]
: ["==", "!=", ">", "<", ">=", "<="];
const [fieldOperation, fieldValue] = value || [possibleOperations[0], undefined];
const [operation, setOperation] = useState<TableWhereFilterOp>(fieldOperation);
const [internalValue, setInternalValue] = useState<Date | undefined>(fieldValue);
function updateFilter(op: TableWhereFilterOp, val: Date | undefined) {
let newValue: Date | undefined = val;
const prevOpIsArray = multipleSelectOperations.includes(operation);
const newOpIsArray = multipleSelectOperations.includes(op);
if (prevOpIsArray !== newOpIsArray) {
// @ts-ignore
newValue = newOpIsArray ? (val ? [val] : []) : "";
}
setOperation(op);
setInternalValue(newValue === null ? undefined : newValue);
const hasNewValue = newValue !== null && Array.isArray(newValue)
? newValue.length > 0
: newValue !== undefined;
if (op && hasNewValue) {
setValue(
[op, newValue]
);
} else {
setValue(
undefined
);
}
}
return (
<Box display={"flex"} width={340} alignItems={"center"}>
<Box width={80}>
<FormControl fullWidth>
<MuiSelect value={operation}
fullWidth
onChange={(evt: any) => {
updateFilter(evt.target.value, internalValue);
}}>
{possibleOperations.map((op) =>
<MenuItem
key={`filter_op_${name}_${op}`}
value={op}>{operationLabels[op]}</MenuItem>
)}
</MuiSelect>
</FormControl>
</Box>
<Box flexGrow={1} ml={1}>
<DateTimePicker
clearable
InputProps={{
// disableUnderline: true,
}}
renderInput={(props) => (
<Input
startAdornment={
<CalendarTodayIcon fontSize={"small"}/>
}
/>
)}
value={internalValue ?? null}
onChange={(dateValue: Date | null) => {
updateFilter(operation, dateValue === null ? undefined : dateValue);
}}
/>
</Box>
{internalValue !== undefined && <Box ml={1}>
<IconButton
onClick={(e) => updateFilter(operation, undefined)}
size={"small"}>
<Tooltip title={`Clear ${title}`}>
<ClearIcon fontSize={"small"}/>
</Tooltip>
</IconButton>
</Box>}
</Box>
);
}
Example #7
Source File: TransactionList.tsx From abrechnung with GNU Affero General Public License v3.0 | 4 votes |
export default function TransactionList({ group }) {
const [speedDialOpen, setSpeedDialOpen] = useState(false);
const toggleSpeedDial = () => setSpeedDialOpen((currValue) => !currValue);
const [showTransferCreateDialog, setShowTransferCreateDialog] = useState(false);
const [showPurchaseCreateDialog, setShowPurchaseCreateDialog] = useState(false);
const transactions = useRecoilValue(transactionsSeenByUser(group.id));
const currentUser = useRecoilValue(userData);
const userPermissions = useRecoilValue(currUserPermissions(group.id));
const userAccounts = useRecoilValue(accountsOwnedByUser({ groupID: group.id, userID: currentUser.id }));
const groupAccountMap = useRecoilValue(accountIDsToName(group.id));
const theme: Theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));
const [filteredTransactions, setFilteredTransactions] = useState([]);
const [searchValue, setSearchValue] = useState("");
const [sortMode, setSortMode] = useState("last_changed"); // last_changed, description, value, billed_at
useEffect(() => {
let filtered = transactions;
if (searchValue != null && searchValue !== "") {
filtered = transactions.filter((t) => t.filter(searchValue, groupAccountMap));
}
filtered = [...filtered].sort(getTransactionSortFunc(sortMode));
setFilteredTransactions(filtered);
}, [searchValue, setFilteredTransactions, sortMode, transactions, userAccounts]);
useTitle(`${group.name} - Transactions`);
const openPurchaseCreateDialog = () => {
setShowPurchaseCreateDialog(true);
};
const openTransferCreateDialog = () => {
setShowTransferCreateDialog(true);
};
return (
<>
<MobilePaper>
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "column", md: "row", lg: "row" },
alignItems: { md: "flex-end" },
pl: "16px",
justifyContent: "space-between",
}}
>
<Box sx={{ display: "flex-item" }}>
<Box sx={{ minWidth: "56px", pt: "16px" }}>
<SearchIcon sx={{ color: "action.active" }} />
</Box>
<Input
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
placeholder="Search…"
inputProps={{
"aria-label": "search",
}}
sx={{ pt: "16px" }}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="clear search input"
onClick={(e) => setSearchValue("")}
edge="end"
>
<Clear />
</IconButton>
</InputAdornment>
}
/>
<FormControl variant="standard" sx={{ minWidth: 120, ml: 3 }}>
<InputLabel id="select-sort-by-label">Sort by</InputLabel>
<Select
labelId="select-sort-by-label"
id="select-sort-by"
label="Sort by"
onChange={(evt) => setSortMode(evt.target.value)}
value={sortMode}
>
<MenuItem value="last_changed">Last changed</MenuItem>
<MenuItem value="description">Description</MenuItem>
<MenuItem value="value">Value</MenuItem>
<MenuItem value="billed_at">Date</MenuItem>
</Select>
</FormControl>
</Box>
{!isSmallScreen && (
<Box sx={{ display: "flex-item" }}>
<div style={{ padding: "8px" }}>
<Add color="primary" />
</div>
<Tooltip title="Create Purchase">
<IconButton color="primary" onClick={openPurchaseCreateDialog}>
<PurchaseIcon />
</IconButton>
</Tooltip>
<Tooltip title="Create Transfer">
<IconButton color="primary" onClick={openTransferCreateDialog}>
<TransferIcon />
</IconButton>
</Tooltip>
</Box>
)}
</Box>
<Divider sx={{ mt: 1 }} />
<List>
{transactions.length === 0 ? (
<Alert severity="info">No Transactions</Alert>
) : (
filteredTransactions.map((transaction) => (
<TransactionListEntry key={transaction.id} group={group} transaction={transaction} />
))
)}
</List>
<TransferCreateModal
group={group}
show={showTransferCreateDialog}
onClose={(evt, reason) => {
if (reason !== "backdropClick") {
setShowTransferCreateDialog(false);
}
}}
/>
<PurchaseCreateModal
group={group}
show={showPurchaseCreateDialog}
onClose={(evt, reason) => {
if (reason !== "backdropClick") {
setShowPurchaseCreateDialog(false);
}
}}
/>
</MobilePaper>
{userPermissions.can_write && (
<SpeedDial
ariaLabel="Create Account"
sx={{ position: "fixed", bottom: 20, right: 20 }}
icon={<SpeedDialIcon />}
// onClose={() => setSpeedDialOpen(false)}
// onOpen={() => setSpeedDialOpen(true)}
onClick={toggleSpeedDial}
open={speedDialOpen}
>
<SpeedDialAction
icon={<PurchaseIcon />}
tooltipTitle="Purchase"
tooltipOpen
onClick={openPurchaseCreateDialog}
/>
<SpeedDialAction
icon={<TransferIcon />}
tooltipTitle="Transfer"
tooltipOpen
onClick={openTransferCreateDialog}
/>
</SpeedDial>
)}
</>
);
}
Example #8
Source File: AccountList.tsx From abrechnung with GNU Affero General Public License v3.0 | 4 votes |
export default function AccountList({ group }) {
const [speedDialOpen, setSpeedDialOpen] = useState(false);
const toggleSpeedDial = () => setSpeedDialOpen((currValue) => !currValue);
const [showPersonalAccountCreationModal, setShowPersonalAccountCreationModal] = useState(false);
const [showClearingAccountCreationModal, setShowClearingAccountCreationModal] = useState(false);
const [activeTab, setActiveTab] = useState("personal");
const [searchValuePersonal, setSearchValuePersonal] = useState("");
const [searchValueClearing, setSearchValueClearing] = useState("");
const [showPersonalAccountEditModal, setShowPersonalAccountEditModal] = useState(false);
const [showClearingAccountEditModal, setShowClearingAccountEditModal] = useState(false);
const [clearingAccountToCopy, setClearingAccountToCopy] = useState(undefined);
const [accountToEdit, setAccountToEdit] = useState(null);
const [clearingAccountToEdit, setClearingAccountToEdit] = useState(null);
const setAccounts = useSetRecoilState(groupAccounts(group.id));
const personalAccounts = useRecoilValue(personalAccountsSeenByUser(group.id));
const clearingAccounts = useRecoilValue(clearingAccountsSeenByUser(group.id));
const allAccounts = useRecoilValue(accountsSeenByUser(group.id));
const [accountToDelete, setAccountToDelete] = useState(null);
const userPermissions = useRecoilValue(currUserPermissions(group.id));
const currentUser = useRecoilValue(userData);
const memberIDToUsername = useRecoilValue(groupMemberIDsToUsername(group.id));
const [filteredPersonalAccounts, setFilteredPersonalAccounts] = useState([]);
const [filteredClearingAccounts, setFilteredClearingAccounts] = useState([]);
useEffect(() => {
if (searchValuePersonal != null && searchValuePersonal !== "") {
setFilteredPersonalAccounts(
personalAccounts.filter((t) => {
return (
t.name.toLowerCase().includes(searchValuePersonal.toLowerCase()) ||
t.description.toLowerCase().includes(searchValuePersonal.toLowerCase())
);
})
);
} else {
return setFilteredPersonalAccounts(personalAccounts);
}
}, [personalAccounts, searchValuePersonal, setFilteredPersonalAccounts]);
useEffect(() => {
if (searchValueClearing != null && searchValueClearing !== "") {
setFilteredClearingAccounts(
clearingAccounts.filter((t) => {
return (
t.name.toLowerCase().includes(searchValueClearing.toLowerCase()) ||
t.description.toLowerCase().includes(searchValueClearing.toLowerCase())
);
})
);
} else {
return setFilteredClearingAccounts(clearingAccounts);
}
}, [clearingAccounts, searchValueClearing, setFilteredClearingAccounts]);
useTitle(`${group.name} - Accounts`);
const openAccountEdit = (account) => {
setAccountToEdit(account);
setShowPersonalAccountEditModal(true);
};
const closeAccountEdit = (evt, reason) => {
if (reason !== "backdropClick") {
setShowPersonalAccountEditModal(false);
setAccountToEdit(null);
}
};
const openClearingAccountEdit = (account) => {
setClearingAccountToEdit(account);
setShowClearingAccountEditModal(true);
};
const closeClearingAccountEdit = (evt, reason) => {
if (reason !== "backdropClick") {
setShowClearingAccountEditModal(false);
setClearingAccountToEdit(null);
}
};
const confirmDeleteAccount = () => {
if (accountToDelete !== null) {
deleteAccount({ accountID: accountToDelete })
.then((account) => {
updateAccount(account, setAccounts);
setAccountToDelete(null);
})
.catch((err) => {
toast.error(err);
});
}
};
const openCreateDialog = () => {
setClearingAccountToCopy(undefined);
setShowClearingAccountCreationModal(true);
};
const copyClearingAccount = (account) => {
setClearingAccountToCopy(account);
setShowClearingAccountCreationModal(true);
};
return (
<>
<MobilePaper>
<TabContext value={activeTab}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<TabList onChange={(e, newValue) => setActiveTab(newValue)} centered>
<Tab
value="personal"
label={
<TextBadge badgeContent={personalAccounts.length} color="primary">
<span>Personal Accounts</span>
</TextBadge>
}
/>
<Tab
label={
<TextBadge badgeContent={clearingAccounts.length} color="primary">
<span>Clearing Accounts</span>
</TextBadge>
}
value="clearing"
/>
</TabList>
</Box>
<TabPanel value="personal">
<List>
{personalAccounts.length === 0 ? (
<Alert severity="info">No Accounts</Alert>
) : (
<>
<ListItem>
<Input
value={searchValuePersonal}
onChange={(e) => setSearchValuePersonal(e.target.value)}
placeholder="Search…"
inputProps={{
"aria-label": "search",
}}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="clear search input"
onClick={(e) => setSearchValuePersonal("")}
edge="end"
>
<Clear />
</IconButton>
</InputAdornment>
}
/>
</ListItem>
<Divider />
{filteredPersonalAccounts.map((account) => (
<ListItem sx={{ padding: 0 }} key={account.id}>
<ListItemLink to={`/groups/${group.id}/accounts/${account.id}`}>
<ListItemText
primary={
<div>
<span>{account.name}</span>
{account.owning_user_id === currentUser.id ? (
<span>
, owned by{" "}
<Chip
size="small"
component="span"
color="primary"
label="you"
/>
</span>
) : (
account.owning_user_id !== null && (
<span>
, owned by{" "}
<Chip
size="small"
component="span"
color="secondary"
label={
memberIDToUsername[
account.owning_user_id
]
}
/>
</span>
)
)}
</div>
}
secondary={account.description}
/>
</ListItemLink>
{userPermissions.can_write && (
<ListItemSecondaryAction>
<IconButton
color="primary"
onClick={() => openAccountEdit(account)}
>
<Edit />
</IconButton>
<IconButton
color="error"
onClick={() => setAccountToDelete(account.id)}
>
<Delete />
</IconButton>
</ListItemSecondaryAction>
)}
</ListItem>
))}
</>
)}
</List>
{userPermissions.can_write && (
<>
<Grid container justifyContent="center">
<Tooltip title="Create Personal Account">
<IconButton
color="primary"
onClick={() => setShowPersonalAccountCreationModal(true)}
>
<Add />
</IconButton>
</Tooltip>
</Grid>
<CreateAccountModal
show={showPersonalAccountCreationModal}
onClose={(evt, reason) => {
if (reason !== "backdropClick") {
setShowPersonalAccountCreationModal(false);
}
}}
group={group}
/>
<EditAccountModal
show={showPersonalAccountEditModal}
onClose={closeAccountEdit}
account={accountToEdit}
group={group}
/>
</>
)}
</TabPanel>
<TabPanel value="clearing">
<List>
{clearingAccounts.length === 0 ? (
<Alert severity="info">No Accounts</Alert>
) : (
<>
<ListItem>
<Input
value={searchValueClearing}
onChange={(e) => setSearchValueClearing(e.target.value)}
placeholder="Search…"
inputProps={{
"aria-label": "search",
}}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="clear search input"
onClick={(e) => setSearchValueClearing("")}
edge="end"
>
<Clear />
</IconButton>
</InputAdornment>
}
/>
</ListItem>
<Divider />
{filteredClearingAccounts.map((account) => (
<ListItem sx={{ padding: 0 }} key={account.id}>
<ListItemLink to={`/groups/${group.id}/accounts/${account.id}`}>
<ListItemText primary={account.name} secondary={account.description} />
</ListItemLink>
{userPermissions.can_write && (
<ListItemSecondaryAction>
<IconButton
color="primary"
onClick={() => openClearingAccountEdit(account)}
>
<Edit />
</IconButton>
<IconButton
color="primary"
onClick={() => copyClearingAccount(account)}
>
<ContentCopy />
</IconButton>
<IconButton
color="error"
onClick={() => setAccountToDelete(account.id)}
>
<Delete />
</IconButton>
</ListItemSecondaryAction>
)}
</ListItem>
))}
</>
)}
</List>
{userPermissions.can_write && (
<>
<Grid container justifyContent="center">
<Tooltip title="Create Clearing Account">
<IconButton color="primary" onClick={openCreateDialog}>
<Add />
</IconButton>
</Tooltip>
</Grid>
<CreateClearingAccountModal
show={showClearingAccountCreationModal}
onClose={(evt, reason) => {
if (reason !== "backdropClick") {
setShowClearingAccountCreationModal(false);
}
}}
initialValues={clearingAccountToCopy}
group={group}
/>
<EditClearingAccountModal
show={showClearingAccountEditModal}
onClose={closeClearingAccountEdit}
account={clearingAccountToEdit}
group={group}
/>
</>
)}
</TabPanel>
</TabContext>
</MobilePaper>
{userPermissions.can_write && (
<>
<SpeedDial
ariaLabel="Create Account"
sx={{ position: "fixed", bottom: 20, right: 20 }}
icon={<SpeedDialIcon />}
// onClose={() => setSpeedDialOpen(false)}
// onOpen={() => setSpeedDialOpen(true)}
onClick={toggleSpeedDial}
open={speedDialOpen}
>
<SpeedDialAction
icon={<PersonalAccountIcon />}
tooltipTitle="Personal"
tooltipOpen
onClick={() => setShowPersonalAccountCreationModal(true)}
/>
<SpeedDialAction
icon={<ClearingAccountIcon />}
tooltipTitle="Clearing"
tooltipOpen
onClick={openCreateDialog}
/>
</SpeedDial>
<Dialog maxWidth="xs" aria-labelledby="confirmation-dialog-title" open={accountToDelete !== null}>
<DialogTitle id="confirmation-dialog-title">Confirm delete account</DialogTitle>
<DialogContent dividers>
Are you sure you want to delete the account "
{allAccounts.find((acc) => acc.id === accountToDelete)?.name}"
</DialogContent>
<DialogActions>
<Button autoFocus onClick={() => setAccountToDelete(null)} color="primary">
Cancel
</Button>
<Button onClick={confirmDeleteAccount} color="error">
Ok
</Button>
</DialogActions>
</Dialog>
</>
)}
</>
);
}
Example #9
Source File: Files.tsx From NekoMaid with MIT License | 4 votes |
Files: React.FC = () => {
const plugin = usePlugin()
const theme = useTheme()
const his = useHistory()
const loc = useLocation()
const drawerWidth = useDrawerWidth()
const tree = useRef<HTMLHRElement | null>(null)
const editor = useRef<UnControlled | null>(null)
const prevExpanded = useRef<string[]>([])
const dirs = useRef<Record<string, boolean>>({ })
// eslint-disable-next-line func-call-spacing
const loading = useRef<Record<string, () => Promise<void>> & { '!#LOADING'?: boolean }>({ })
const [id, setId] = useState(0)
const [curPath, setCurPath] = useState('')
const [progress, setProgress] = useState(-1)
const [copyPath, setCopyPath] = useState('')
const [expanded, setExpanded] = useState<string[]>([])
const [compressFile, setCompressFile] = useState<string | null>(null)
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
const isDir = !!dirs.current[curPath]
const dirPath = isDir ? curPath : curPath.substring(0, curPath.lastIndexOf('/'))
const spacing = theme.spacing(3)
const refresh = () => {
loading.current = { }
dirs.current = { }
prevExpanded.current = []
setCurPath('')
setExpanded([])
setId(id + 1)
}
useEffect(() => {
if (!tree.current) return
const resize = () => {
if (!tree.current) return
const height = tree.current.style.maxHeight = (window.innerHeight - tree.current.offsetTop - parseInt(spacing)) + 'px'
const style = (editor as any).current?.editor?.display?.wrapper?.style
if (style) style.height = height
}
resize()
window.addEventListener('resize', resize)
return window.removeEventListener('resize', resize)
}, [tree.current, spacing])
return <Box sx={{ height: '100vh', py: 3 }}>
<Toolbar />
<Container maxWidth={false}>
<Grid container spacing={3} sx={{ width: { sm: `calc(100vw - ${drawerWidth}px - ${theme.spacing(3)})` } }}>
<Grid item lg={4} md={12} xl={3} xs={12}>
<Card sx={{ minHeight: 400 }}>
<CardHeader
title={lang.files.filesList}
sx={{ position: 'relative' }}
action={<Box sx={{ position: 'absolute', right: theme.spacing(1), top: '50%', transform: 'translateY(-50%)' }}
>
<Tooltip title={lang.files.delete}><span>
<IconButton
disabled={!curPath}
size='small'
onClick={() => dialog({
okButton: { color: 'error' },
content: <>{lang.files.confirmDelete(<span className='bold'>{curPath}</span>)}
<span className='bold' style={{ color: theme.palette.error.main }}>({lang.unrecoverable})</span></>
}).then(it => it && plugin.emit('files:update', (res: boolean) => {
action(res)
if (!res) return
refresh()
if (loc.pathname.replace(/^\/NekoMaid\/files\/?/, '') === curPath) his.push('/NekoMaid/files')
}, curPath))}
><DeleteForever /></IconButton>
</span></Tooltip>
<Tooltip title={lang.files.createFile}>
<IconButton size='small' onClick={() => fileNameDialog(lang.files.createFile, curPath)
.then(it => it != null && his.push(`/NekoMaid/files/${dirPath ? dirPath + '/' : ''}${it}`))}>
<Description /></IconButton></Tooltip>
<Tooltip title={lang.files.createFolder}>
<IconButton size='small' onClick={() => fileNameDialog(lang.files.createFolder, curPath)
.then(it => it != null && plugin.emit('files:createDirectory', (res: boolean) => {
action(res)
if (res) refresh()
}, dirPath + '/' + it))}><CreateNewFolder /></IconButton></Tooltip>
<Tooltip title={lang.more}>
<IconButton size='small' onClick={e => setAnchorEl(anchorEl ? null : e.currentTarget)}><MoreHoriz /></IconButton>
</Tooltip>
</Box>} />
<Divider />
<TreeView
ref={tree}
defaultCollapseIcon={<ArrowDropDown />}
defaultExpandIcon={<ArrowRight />}
sx={{ flexGrow: 1, width: '100%', overflowY: 'auto' }}
expanded={expanded}
onNodeToggle={(_: any, it: string[]) => {
const l = loading.current
if (it.length < prevExpanded.current.length || !l[it[0]]) {
setExpanded(it)
prevExpanded.current = it
return
}
l[it[0]]().then(() => {
prevExpanded.current.unshift(it[0])
setExpanded([...prevExpanded.current])
delete l[it[0]]
})
delete l[it[0]]
}}
onNodeSelect={(_: any, it: string) => {
setCurPath(it[0] === '/' ? it.slice(1) : it)
if (dirs.current[it] || loading.current['!#LOADING']) return
if (it.startsWith('/')) it = it.slice(1)
his.push('/NekoMaid/files/' + it)
}}
>
<Item plugin={plugin} path='' loading={loading.current} dirs={dirs.current} key={id} />
</TreeView>
</Card>
</Grid>
<Grid item lg={8} md={12} xl={9} xs={12} sx={{ maxWidth: `calc(100vw - ${theme.spacing(1)})`, paddingBottom: 3 }}>
<Editor plugin={plugin} editorRef={editor} loading={loading.current} dirs={dirs.current} refresh={refresh} />
</Grid>
</Grid>
</Container>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
anchorOrigin={anchorOrigin}
transformOrigin={anchorOrigin}
>
<MenuItem onClick={() => {
refresh()
setAnchorEl(null)
}}><ListItemIcon><Refresh /></ListItemIcon>{lang.refresh}</MenuItem>
<MenuItem disabled={!curPath} onClick={() => {
setAnchorEl(null)
fileNameDialog(lang.files.rename, curPath).then(it => it != null && plugin.emit('files:rename', (res: boolean) => {
action(res)
if (res) refresh()
}, curPath, dirPath + '/' + it))
}}><ListItemIcon><DriveFileRenameOutline /></ListItemIcon>{lang.files.rename}</MenuItem>
<MenuItem disabled={!curPath} onClick={() => {
setAnchorEl(null)
setCopyPath(curPath)
}}>
<ListItemIcon><FileCopy /></ListItemIcon>{lang.files.copy}
</MenuItem>
<MenuItem disabled={!copyPath} onClick={() => {
setAnchorEl(null)
toast(lang.files.pasting)
plugin.emit('files:copy', (res: boolean) => {
action(res)
refresh()
}, copyPath, dirPath)
}}>
<ListItemIcon><ContentPaste /></ListItemIcon>{lang.files.paste}
</MenuItem>
<MenuItem disabled={progress !== -1} component='label' htmlFor='NekoMaid-files-upload-input' onClick={() => setAnchorEl(null)}>
<ListItemIcon><Upload /></ListItemIcon>{progress === -1 ? lang.files.upload : `${lang.files.uploading} (${progress.toFixed(2)}%)`}
</MenuItem>
<MenuItem disabled={isDir} onClick={() => {
setAnchorEl(null)
toast(lang.files.downloading)
plugin.emit('files:download', (res: ArrayBuffer | null) => {
if (res) window.open(address! + 'Download/' + res, '_blank')
else failed()
}, curPath)
}}><ListItemIcon><Download /></ListItemIcon>{lang.files.download}</MenuItem>
<MenuItem onClick={() => {
setAnchorEl(null)
setCompressFile(curPath)
}}><ListItemIcon><Inbox /></ListItemIcon>{lang.files.compress}</MenuItem>
<MenuItem onClick={() => {
setAnchorEl(null)
toast(lang.files.uncompressing)
plugin.emit('files:compress', (res: boolean) => {
action(res)
refresh()
}, curPath)
}}><ListItemIcon><Outbox /></ListItemIcon>{lang.files.decompress}</MenuItem>
</Menu>
<Input id='NekoMaid-files-upload-input' type='file' sx={{ display: 'none' }} onChange={e => {
const elm = e.target as HTMLInputElement
const file = elm.files?.[0]
elm.value = ''
if (!file) return
const size = file.size
if (size > 128 * 1024 * 1024) return failed(lang.files.uploadTooBig)
toast(lang.files.uploading)
const name = dirPath + '/' + file.name
if (dirs.current[name] != null) return failed(lang.files.exists)
plugin.emit('files:upload', (res: string | null) => {
if (!res) return failed(lang.files.exists)
const formdata = new FormData()
formdata.append('file', file)
const xhr = new XMLHttpRequest()
setProgress(0)
xhr.open('put', address! + 'Upload/' + res)
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return
setProgress(-1)
action(xhr.status === 200)
refresh()
}
xhr.upload.onprogress = e => e.lengthComputable && setProgress(e.loaded / e.total * 100)
xhr.send(formdata)
}, name[0] === '/' ? name.slice(1) : name)
}} />
<CompressDialog file={compressFile} path={dirPath} dirs={dirs.current} onClose={() => setCompressFile(null)} refresh={refresh} plugin={plugin} />
</Box>
}
Example #10
Source File: Deposit.tsx From wrap.scrt.network with MIT License | 4 votes |
export default function Deposit({
token,
secretAddress,
onSuccess,
onFailure,
}: {
token: Token;
secretAddress: string;
onSuccess: (txhash: string) => any;
onFailure: (error: any) => any;
}) {
const [sourceAddress, setSourceAddress] = useState<string>("");
const [availableBalance, setAvailableBalance] = useState<string>("");
const [loadingTx, setLoading] = useState<boolean>(false);
const [sourceCosmJs, setSourceCosmJs] =
useState<SigningStargateClient | null>(null);
const [selectedChainIndex, setSelectedChainIndex] = useState<number>(0);
const [fetchBalanceInterval, setFetchBalanceInterval] = useState<any>(null);
const inputRef = useRef<any>();
const maxButtonRef = useRef<any>();
const sourceChain =
chains[token.deposits[selectedChainIndex].source_chain_name];
const targetChain = chains["Secret Network"];
const fetchSourceBalance = async (sourceAddress: string) => {
const url = `${
chains[token.deposits[selectedChainIndex].source_chain_name].lcd
}/bank/balances/${sourceAddress}`;
try {
const response = await fetch(url);
const result: {
height: string;
result: Array<{ denom: string; amount: string }>;
} = await response.json();
const balance =
result.result.find(
(c) => c.denom === token.deposits[selectedChainIndex].from_denom
)?.amount || "0";
setAvailableBalance(balance);
} catch (e) {
console.error(`Error while trying to query ${url}:`, e);
setAvailableBalance("Error");
}
};
useEffect(() => {
setAvailableBalance("");
if (!sourceAddress) {
return;
}
if (fetchBalanceInterval) {
clearInterval(fetchBalanceInterval);
}
fetchSourceBalance(sourceAddress);
const interval = setInterval(
() => fetchSourceBalance(sourceAddress),
10_000
);
setFetchBalanceInterval(interval);
return () => clearInterval(interval);
}, [sourceAddress]);
useEffect(() => {
(async () => {
while (!window.keplr || !window.getOfflineSignerOnlyAmino) {
await sleep(100);
}
if (["LUNA", "UST"].includes(token.name.toUpperCase())) {
await suggestTerraToKeplr(window.keplr);
}
// Initialize cosmjs on the target chain, because it has sendIbcTokens()
const { chain_id, rpc, bech32_prefix } =
chains[token.deposits[selectedChainIndex].source_chain_name];
await window.keplr.enable(chain_id);
const sourceOfflineSigner = window.getOfflineSignerOnlyAmino(chain_id);
const depositFromAccounts = await sourceOfflineSigner.getAccounts();
setSourceAddress(depositFromAccounts[0].address);
const cosmjs = await SigningStargateClient.connectWithSigner(
rpc,
sourceOfflineSigner,
{ prefix: bech32_prefix, broadcastPollIntervalMs: 10_000 }
);
setSourceCosmJs(cosmjs);
})();
}, [selectedChainIndex]);
return (
<>
<div style={{ padding: "1.5em" }}>
<div
style={{
display: "flex",
placeItems: "center",
gap: token.deposits.length === 1 ? "0.3em" : "0.5em",
}}
>
<Typography>
Deposit <strong>{token.name}</strong> from
</Typography>
<If condition={token.deposits.length === 1}>
<Then>
<Typography>
<strong>
{token.deposits[selectedChainIndex].source_chain_name}
</strong>
</Typography>
</Then>
<Else>
<FormControl>
<Select
value={selectedChainIndex}
onChange={(e) =>
setSelectedChainIndex(Number(e.target.value))
}
>
{token.deposits.map((chain, index) => (
<MenuItem value={index} key={index}>
<div
style={{
display: "flex",
gap: "0.5em",
placeItems: "center",
}}
>
<Avatar
src={chains[chain.source_chain_name].chain_image}
sx={{
marginLeft: "0.3em",
width: "1em",
height: "1em",
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
<strong>{chain.source_chain_name}</strong>
</div>
</MenuItem>
))}
</Select>
</FormControl>
</Else>
</If>
<Typography>
to <strong>Secret Network</strong>
</Typography>
</div>
<br />
<div
style={{
display: "flex",
placeContent: "space-between",
placeItems: "center",
gap: "1em",
}}
>
<Typography sx={{ fontWeight: "bold" }}>From:</Typography>
<CopyableAddress
address={sourceAddress}
explorerPrefix={sourceChain.explorer_account}
/>
</div>
<div
style={{
display: "flex",
placeContent: "space-between",
placeItems: "center",
gap: "1em",
}}
>
<Typography sx={{ fontWeight: "bold" }}>To:</Typography>
<CopyableAddress
address={secretAddress}
explorerPrefix={targetChain.explorer_account}
/>
</div>
<br />
<div
style={{
display: "flex",
placeItems: "center",
gap: "0.3em",
marginBottom: "0.8em",
}}
>
<Typography sx={{ fontSize: "0.8em", fontWeight: "bold" }}>
Available to Deposit:
</Typography>
<Typography
sx={{
fontSize: "0.8em",
opacity: 0.8,
cursor: "pointer",
}}
onClick={() => {
maxButtonRef.current.click();
}}
>
{(() => {
if (availableBalance === "") {
return <CircularProgress size="0.6em" />;
}
const prettyBalance = new BigNumber(availableBalance)
.dividedBy(`1e${token.decimals}`)
.toFormat();
if (prettyBalance === "NaN") {
return "Error";
}
return `${prettyBalance} ${token.name}`;
})()}
</Typography>
</div>
<FormControl sx={{ width: "100%" }} variant="standard">
<InputLabel htmlFor="Amount to Deposit">Amount to Deposit</InputLabel>
<Input
autoFocus
id="Amount to Deposit"
fullWidth
type="text"
inputRef={inputRef}
startAdornment={
<InputAdornment position="start">
<Avatar
src={token.image}
sx={{
width: "1em",
height: "1em",
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
<Button
ref={maxButtonRef}
style={{
padding: "0.1em 0.5em",
minWidth: 0,
}}
onClick={() => {
if (availableBalance === "") {
return;
}
const prettyBalance = new BigNumber(availableBalance)
.dividedBy(`1e${token.decimals}`)
.toFormat();
if (prettyBalance === "NaN") {
return;
}
inputRef.current.value = prettyBalance;
}}
>
MAX
</Button>
</InputAdornment>
}
/>
</FormControl>
</div>
<div
style={{
display: "flex",
placeContent: "center",
marginBottom: "0.4em",
}}
>
<LoadingButton
variant="contained"
sx={{
padding: "0.5em 0",
width: "10em",
fontWeight: "bold",
fontSize: "1.2em",
}}
loading={loadingTx}
onClick={async () => {
if (!sourceCosmJs) {
console.error("No cosmjs");
return;
}
if (!inputRef?.current?.value) {
console.error("Empty deposit");
return;
}
const normalizedAmount = (inputRef.current.value as string).replace(
/,/g,
""
);
if (!(Number(normalizedAmount) > 0)) {
console.error(`${normalizedAmount} not bigger than 0`);
return;
}
setLoading(true);
const amount = new BigNumber(normalizedAmount)
.multipliedBy(`1e${token.decimals}`)
.toFixed(0, BigNumber.ROUND_DOWN);
const { deposit_channel_id, deposit_gas } =
chains[token.deposits[selectedChainIndex].source_chain_name];
try {
const { transactionHash } = await sourceCosmJs.sendIbcTokens(
sourceAddress,
secretAddress,
{
amount,
denom: token.deposits[selectedChainIndex].from_denom,
},
"transfer",
deposit_channel_id,
undefined,
Math.floor(Date.now() / 1000) + 15 * 60, // 15 minute timeout
gasToFee(deposit_gas)
);
inputRef.current.value = "";
onSuccess(transactionHash);
} catch (e) {
onFailure(e);
} finally {
setLoading(false);
}
}}
>
Deposit
</LoadingButton>
</div>
</>
);
}
Example #11
Source File: TokenRow.tsx From wrap.scrt.network with MIT License | 4 votes |
export default function TokenRow({
secretjs,
secretAddress,
token,
balances,
loadingCoinBalances,
price,
}: {
secretjs: SecretNetworkClient | null;
secretAddress: string;
loadingCoinBalances: boolean;
token: Token;
balances: Map<string, string>;
price: number;
}) {
const wrapInputRef = useRef<any>();
const [loadingWrap, setLoadingWrap] = useState<boolean>(false);
const [loadingUnwrap, setLoadingUnwrap] = useState<boolean>(false);
const [tokenBalance, setTokenBalance] = useState<string>("");
const [loadingTokenBalance, setLoadingTokenBalance] =
useState<boolean>(false);
const [isDepositWithdrawDialogOpen, setIsDepositWithdrawDialogOpen] =
useState<boolean>(false);
const updateTokenBalance = async () => {
if (!token.address) {
return;
}
if (!secretjs) {
return;
}
const key = await getKeplrViewingKey(token.address);
if (!key) {
setTokenBalance(viewingKeyErrorString);
return;
}
try {
const result = await secretjs.query.compute.queryContract({
address: token.address,
codeHash: token.code_hash,
query: {
balance: { address: secretAddress, key },
},
});
if (result.viewing_key_error) {
setTokenBalance(viewingKeyErrorString);
return;
}
setTokenBalance(result.balance.amount);
} catch (e) {
console.error(`Error getting balance for s${token.name}`, e);
setTokenBalance(viewingKeyErrorString);
}
};
useEffect(() => {
(async () => {
try {
setLoadingTokenBalance(true);
await updateTokenBalance();
} finally {
setLoadingTokenBalance(false);
}
})();
}, [secretjs]);
const denomOnSecret = token.withdrawals[0]?.from_denom;
let balanceIbcCoin;
let balanceToken;
if (token.address) {
if (loadingCoinBalances) {
balanceIbcCoin = (
<div>
<div>
Balance: <CircularProgress size="0.8em" />
</div>
<div style={{ opacity: 0 }}>placeholder</div>
</div>
);
} else if (balances.get(denomOnSecret)) {
balanceIbcCoin = (
<div>
<div
style={{ cursor: "pointer" }}
onClick={() => {
wrapInputRef.current.value = new BigNumber(
balances.get(denomOnSecret)!
)
.dividedBy(`1e${token.decimals}`)
.toFixed();
}}
>
{`Balance: ${new BigNumber(balances.get(denomOnSecret)!)
.dividedBy(`1e${token.decimals}`)
.toFormat()}`}
</div>
<div style={{ display: "flex", opacity: 0.7 }}>
{usdString.format(
new BigNumber(balances.get(denomOnSecret)!)
.dividedBy(`1e${token.decimals}`)
.multipliedBy(price)
.toNumber()
)}
</div>
</div>
);
} else {
balanceIbcCoin = (
<div>
<div
style={{ cursor: "pointer" }}
onClick={() => {
document.getElementById("keplr-button")?.click();
}}
>
connect wallet
</div>
<div style={{ opacity: 0 }}>(please)</div>
</div>
);
}
} else {
balanceIbcCoin = (
<div>
<div>coming soon</div>
<div>(?)</div>
</div>
);
}
if (token.address) {
if (!secretjs) {
balanceToken = (
<div>
<div
style={{ cursor: "pointer" }}
onClick={() => {
document.getElementById("keplr-button")?.click();
}}
>
connect wallet
</div>
<div style={{ opacity: 0 }}>(please)</div>
</div>
);
} else if (loadingTokenBalance) {
balanceToken = (
<div>
<div>
Balance: <CircularProgress size="0.8em" />
</div>
<div style={{ opacity: 0 }}>placeholder</div>
</div>
);
} else if (tokenBalance == viewingKeyErrorString) {
balanceToken = (
<div>
<Tooltip title="Set Viewing Key" placement="top">
<div
style={{ cursor: "pointer" }}
onClick={async () => {
await setKeplrViewingKey(token.address);
try {
setLoadingTokenBalance(true);
await sleep(1000); // sometimes query nodes lag
await updateTokenBalance();
} finally {
setLoadingTokenBalance(false);
}
}}
>
{`Balance: ${viewingKeyErrorString}`}
</div>
</Tooltip>
<div style={{ opacity: 0 }}>placeholder</div>
</div>
);
} else if (Number(tokenBalance) > -1) {
balanceToken = (
<div>
<div
style={{ cursor: "pointer" }}
onClick={() => {
wrapInputRef.current.value = new BigNumber(tokenBalance)
.dividedBy(`1e${token.decimals}`)
.toFixed();
}}
>
{`Balance: ${new BigNumber(tokenBalance)
.dividedBy(`1e${token.decimals}`)
.toFormat()}`}
</div>
<div
style={{ display: "flex", placeContent: "flex-end", opacity: 0.7 }}
>
{usdString.format(
new BigNumber(tokenBalance)
.dividedBy(`1e${token.decimals}`)
.multipliedBy(price)
.toNumber()
)}
</div>
</div>
);
}
} else {
balanceToken = (
<div>
<div>coming soon</div>
<div style={{ display: "flex", placeContent: "flex-end" }}>(?)</div>
</div>
);
}
return (
<>
<Breakpoint small down>
<div
style={{
display: "flex",
placeContent: "space-between",
placeItems: "center",
padding: "0.8rem",
fontSize: "1.2rem",
}}
>
<Avatar
src={token.image}
sx={{
width: 44,
height: 44,
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
<div>{token.name}</div>
<div style={{ display: "flex" }}>
<If condition={token.address}>
<Then>
<CompareArrowsIcon style={{ transform: "scale(0.9)" }} />
</Then>
<Else>
<div style={{ fontSize: "0.65rem" }}>(soon™)</div>
</Else>
</If>
</div>
<div>s{token.name}</div>
<Avatar
src={token.image}
sx={{
width: 44,
height: 44,
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
</div>
</Breakpoint>
<Breakpoint medium up>
<div
style={{
display: "flex",
placeItems: "center",
gap: "0.8rem",
padding: "0.4rem",
borderRadius: 20,
}}
>
<span style={{ flex: 1 }}></span>
<Avatar
src={token.image}
sx={{
width: 38,
height: 38,
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
<div
style={{
display: "flex",
width: 150,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
placeItems: "flex-start",
}}
>
<div
style={{
display: "flex",
placeContent: "space-between",
placeItems: "center",
minWidth: "5.6em",
}}
>
<span>{token.name}</span>
<When condition={token.address && secretAddress}>
<>
<Tooltip title={`IBC Deposit & Withdraw`} placement="top">
<Button
style={{
minWidth: 0,
padding: 0,
marginBottom: "0.2em",
color: "black",
opacity: 0.8,
}}
onClick={async () =>
setIsDepositWithdrawDialogOpen(true)
}
>
<CompareArrowsIcon sx={{ height: "0.8em" }} />
</Button>
</Tooltip>
<DepositWithdrawDialog
token={token}
balances={balances}
secretAddress={secretAddress}
secretjs={secretjs}
isOpen={isDepositWithdrawDialogOpen}
setIsOpen={setIsDepositWithdrawDialogOpen}
/>
</>
</When>
</div>
<span style={{ fontSize: "0.75rem" }}>{balanceIbcCoin}</span>
</div>
</div>
<div
style={{
display: "flex",
placeItems: "center",
gap: "0.3rem",
}}
>
<Button
disabled={token.address === ""}
size="small"
variant="text"
startIcon={
<If condition={loadingUnwrap}>
<Then>
<CircularProgress size="0.8em" />
</Then>
<Else>
<KeyboardArrowLeftIcon />
</Else>
</If>
}
onClick={async () => {
if (
!secretjs ||
!secretAddress ||
loadingWrap ||
loadingUnwrap
) {
return;
}
const baseAmount = wrapInputRef?.current?.value;
const amount = new BigNumber(baseAmount)
.multipliedBy(`1e${token.decimals}`)
.toFixed(0, BigNumber.ROUND_DOWN);
if (amount === "NaN") {
console.error("NaN amount", baseAmount);
return;
}
setLoadingUnwrap(true);
try {
const tx = await secretjs.tx.broadcast(
[
new MsgExecuteContract({
sender: secretAddress,
contract: token.address,
codeHash: token.code_hash,
sentFunds: [],
msg: {
redeem: {
amount,
denom:
token.name === "SCRT"
? undefined
: token.withdrawals[0].from_denom,
},
},
}),
],
{
gasLimit: 40_000,
gasPriceInFeeDenom: 0.25,
feeDenom: "uscrt",
}
);
if (tx.code === 0) {
wrapInputRef.current.value = "";
console.log(`Unwrapped successfully`);
} else {
console.error(`Tx failed: ${tx.rawLog}`);
}
} finally {
setLoadingUnwrap(false);
try {
setLoadingTokenBalance(true);
await sleep(1000); // sometimes query nodes lag
await updateTokenBalance();
} finally {
setLoadingTokenBalance(false);
}
}
}}
>
<Breakpoint medium up>
Unwrap
</Breakpoint>
</Button>
<Input
disabled={token.address === ""}
// TODO add input validation
placeholder="Amount"
inputProps={{
style: {
textAlign: "center",
textOverflow: "ellipsis",
},
}}
inputRef={wrapInputRef}
/>
<Button
disabled={token.address === ""}
size="small"
variant="text"
endIcon={
loadingWrap ? (
<CircularProgress size="0.8em" />
) : (
<KeyboardArrowRightIcon />
)
}
onClick={async () => {
if (
!secretjs ||
!secretAddress ||
loadingWrap ||
loadingUnwrap
) {
return;
}
const baseAmount = wrapInputRef?.current?.value;
const amount = new BigNumber(baseAmount)
.multipliedBy(`1e${token.decimals}`)
.toFixed(0, BigNumber.ROUND_DOWN);
if (amount === "NaN") {
console.error("NaN amount", baseAmount);
return;
}
setLoadingWrap(true);
try {
const tx = await secretjs.tx.broadcast(
[
new MsgExecuteContract({
sender: secretAddress,
contract: token.address,
codeHash: token.code_hash,
sentFunds: [
{ denom: token.withdrawals[0].from_denom, amount },
],
msg: { deposit: {} },
}),
],
{
gasLimit: 40_000,
gasPriceInFeeDenom: 0.25,
feeDenom: "uscrt",
}
);
if (tx.code === 0) {
wrapInputRef.current.value = "";
console.log(`Wrapped successfully`);
} else {
console.error(`Tx failed: ${tx.rawLog}`);
}
} finally {
setLoadingWrap(false);
try {
setLoadingTokenBalance(true);
await sleep(1000); // sometimes query nodes lag
await updateTokenBalance();
} finally {
setLoadingTokenBalance(false);
}
}
}}
>
<Breakpoint medium up>
Wrap
</Breakpoint>
</Button>
</div>
<div
style={{
display: "flex",
width: 150,
placeContent: "flex-end",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
placeItems: "flex-end",
}}
>
<span>s{token.name}</span>
<div
style={{
fontSize: "0.75rem",
display: "flex",
placeItems: "flex-start",
gap: "0.2em",
}}
>
<span>{balanceToken}</span>
<When condition={token.address && secretAddress}>
<Tooltip title="Refresh Balance" placement="top">
<Button
style={{
color: "black",
minWidth: 0,
padding: 0,
display: loadingTokenBalance ? "none" : undefined,
}}
onClick={async () => {
try {
setLoadingTokenBalance(true);
await updateTokenBalance();
} finally {
setLoadingTokenBalance(false);
}
}}
>
<RefreshIcon sx={{ height: "0.7em" }} />
</Button>
</Tooltip>
</When>
</div>
</div>
</div>
<Avatar
src={token.image}
sx={{
width: 38,
height: 38,
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
<span style={{ flex: 1 }}></span>
</div>
</Breakpoint>
</>
);
}
Example #12
Source File: Withdraw.tsx From wrap.scrt.network with MIT License | 4 votes |
export default function Withdraw({
token,
secretjs,
secretAddress,
balances,
onSuccess,
onFailure,
}: {
token: Token;
secretjs: SecretNetworkClient | null;
secretAddress: string;
balances: Map<string, string>;
onSuccess: (txhash: string) => any;
onFailure: (error: any) => any;
}) {
const [targetAddress, setTargetAddress] = useState<string>("");
const [loadingTx, setLoading] = useState<boolean>(false);
const [selectedChainIndex, setSelectedChainIndex] = useState<number>(0);
const inputRef = useRef<any>();
const maxButtonRef = useRef<any>();
const sourceChain = chains["Secret Network"];
const targetChain =
chains[token.withdrawals[selectedChainIndex].target_chain_name];
const availableBalance =
balances.get(token.withdrawals[selectedChainIndex].from_denom) || "";
useEffect(() => {
(async () => {
while (!window.keplr || !window.getOfflineSignerOnlyAmino) {
await sleep(100);
}
// Find address on target chain
const { chain_id: targetChainId } =
chains[token.withdrawals[selectedChainIndex].target_chain_name];
if (token.withdrawals[selectedChainIndex].target_chain_name === "Terra") {
await suggestTerraToKeplr(window.keplr);
}
await window.keplr.enable(targetChainId);
const targetOfflineSigner =
window.getOfflineSignerOnlyAmino(targetChainId);
const targetFromAccounts = await targetOfflineSigner.getAccounts();
setTargetAddress(targetFromAccounts[0].address);
})();
}, [selectedChainIndex]);
return (
<>
<div style={{ padding: "1.5em" }}>
<div
style={{
display: "flex",
placeItems: "center",
gap: "0.5em",
}}
>
<Typography>
Withdraw <strong>{token.name}</strong> from{" "}
<strong>Secret Network</strong> to
</Typography>
<If condition={token.withdrawals.length === 1}>
<Then>
<Typography sx={{ marginLeft: "-0.2em" }}>
<strong>
{token.withdrawals[selectedChainIndex].target_chain_name}
</strong>
</Typography>
</Then>
<Else>
<FormControl>
<Select
value={selectedChainIndex}
onChange={(e) =>
setSelectedChainIndex(Number(e.target.value))
}
>
{token.withdrawals.map((chain, index) => (
<MenuItem value={index} key={index}>
<div
style={{
display: "flex",
gap: "0.5em",
placeItems: "center",
}}
>
<Avatar
src={chains[chain.target_chain_name].chain_image}
sx={{
marginLeft: "0.3em",
width: "1em",
height: "1em",
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
<strong>{chain.target_chain_name}</strong>
</div>
</MenuItem>
))}
</Select>
</FormControl>
</Else>
</If>
</div>
<br />
<div
style={{
display: "flex",
placeContent: "space-between",
placeItems: "center",
gap: "1em",
}}
>
<Typography sx={{ fontWeight: "bold" }}>From:</Typography>
<CopyableAddress
address={secretAddress}
explorerPrefix={sourceChain.explorer_account}
/>
</div>
<div
style={{
display: "flex",
placeContent: "space-between",
placeItems: "center",
gap: "1em",
}}
>
<Typography sx={{ fontWeight: "bold" }}>To:</Typography>
<CopyableAddress
address={targetAddress}
explorerPrefix={targetChain.explorer_account}
/>
</div>
<br />
<div
style={{
display: "flex",
placeItems: "center",
gap: "0.3em",
marginBottom: "0.8em",
}}
>
<Typography sx={{ fontSize: "0.8em", fontWeight: "bold" }}>
Available to Withdraw:
</Typography>
<Typography
sx={{
fontSize: "0.8em",
opacity: 0.8,
cursor: "pointer",
}}
onClick={() => {
maxButtonRef.current.click();
}}
>
{(() => {
if (availableBalance === "") {
return <CircularProgress size="0.6em" />;
}
const prettyBalance = new BigNumber(availableBalance)
.dividedBy(`1e${token.decimals}`)
.toFormat();
if (prettyBalance === "NaN") {
return "Error";
}
return `${prettyBalance} ${token.name}`;
})()}
</Typography>
</div>
<FormControl sx={{ width: "100%" }} variant="standard">
<InputLabel htmlFor="Amount to Withdraw">
Amount to Withdraw
</InputLabel>
<Input
autoFocus
id="Amount to Withdraw"
fullWidth
type="text"
inputRef={inputRef}
startAdornment={
<InputAdornment position="start">
<Avatar
src={token.image}
sx={{
width: "1em",
height: "1em",
boxShadow: "rgba(0, 0, 0, 0.15) 0px 6px 10px",
}}
/>
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
<Button
ref={maxButtonRef}
style={{
padding: "0.1em 0.5em",
minWidth: 0,
}}
onClick={() => {
if (availableBalance === "") {
return;
}
const prettyBalance = new BigNumber(availableBalance)
.dividedBy(`1e${token.decimals}`)
.toFormat();
if (prettyBalance === "NaN") {
return;
}
inputRef.current.value = prettyBalance;
}}
>
MAX
</Button>
</InputAdornment>
}
/>
</FormControl>
</div>
<div
style={{
display: "flex",
placeContent: "center",
marginBottom: "0.4em",
}}
>
<LoadingButton
variant="contained"
sx={{
padding: "0.5em 0",
width: "10em",
fontWeight: "bold",
fontSize: "1.2em",
}}
loading={loadingTx}
onClick={async () => {
if (!secretjs) {
console.error("No secretjs");
return;
}
if (!inputRef?.current?.value) {
console.error("Empty withdraw");
return;
}
const normalizedAmount = (inputRef.current.value as string).replace(
/,/g,
""
);
if (!(Number(normalizedAmount) > 0)) {
console.error(`${normalizedAmount} not bigger than 0`);
return;
}
setLoading(true);
const amount = new BigNumber(normalizedAmount)
.multipliedBy(`1e${token.decimals}`)
.toFixed(0, BigNumber.ROUND_DOWN);
const { withdraw_channel_id, withdraw_gas } =
chains[token.withdrawals[selectedChainIndex].target_chain_name];
try {
const tx = await secretjs.tx.broadcast(
[
new MsgTransfer({
sender: secretAddress,
receiver: targetAddress,
sourceChannel: withdraw_channel_id,
sourcePort: "transfer",
token: {
amount,
denom: token.withdrawals[selectedChainIndex].from_denom,
},
timeoutTimestampSec: String(
Math.floor(Date.now() / 1000) + 15 * 60
), // 15 minute timeout
}),
],
{
gasLimit: withdraw_gas,
gasPriceInFeeDenom: 0.25,
feeDenom: "uscrt",
}
);
if (tx.code === 0) {
inputRef.current.value = "";
onSuccess(tx.transactionHash);
} else {
onFailure(tx.rawLog);
}
} catch (e) {
onFailure(e);
} finally {
setLoading(false);
}
}}
>
Withdraw
</LoadingButton>
</div>
</>
);
}