@arcblock/did-playground
Version:
React components that works with wallet-playground
303 lines (279 loc) • 8.49 kB
JSX
import { use, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useSize } from 'ahooks';
import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
import { styled, useTheme } from '@arcblock/ux/lib/Theme';
import isUndefined from 'lodash/isUndefined';
import { CircularProgress, Dialog, DialogContent } from '@mui/material';
import DidConnect from '@arcblock/did-connect/lib/Connect';
import Button from '@arcblock/ux/lib/Button';
import { mergeProps } from '@arcblock/ux/lib/Util';
import { SessionContext } from './session';
import { actions, getMessage, getActionName, getActionParams } from './actions';
function Close({ onClose }) {
return <CloseContainer onClick={onClose}>×</CloseContainer>;
}
Close.propTypes = { onClose: PropTypes.func.isRequired };
const CloseContainer = styled('div')`
display: ${(props) => (props.disableClose ? 'none' : 'block')};
position: absolute;
top: 1rem;
right: 1rem;
color: #999999;
font-size: 2rem;
line-height: 1rem;
cursor: pointer;
user-select: none;
`;
function PlaygroundAction({ ...rawProps }) {
const props = Object.assign({}, rawProps);
if (isUndefined(props.autoClose)) {
props.autoClose = true;
}
if (isUndefined(props.buttonText)) {
props.buttonText = '';
}
if (isUndefined(props.buttonColor)) {
props.buttonColor = 'primary';
}
if (isUndefined(props.buttonVariant)) {
props.buttonVariant = 'contained';
}
if (isUndefined(props.buttonSize)) {
props.buttonSize = 'large';
}
if (isUndefined(props.buttonRounded)) {
props.buttonRounded = false;
}
if (isUndefined(props.scanMessage)) {
props.scanMessage = 'Scan the QR Code with your DID Wallet';
}
if (isUndefined(props.confirmMessage)) {
props.confirmMessage = 'Confirm in your DID Wallet';
}
if (isUndefined(props.successMessage)) {
props.successMessage = 'Operation success!';
}
if (isUndefined(props.extraParams)) {
props.extraParams = {};
}
if (isUndefined(props.timeout)) {
props.timeout = 5 * 60 * 1000;
}
if (isUndefined(props.successUrl)) {
props.successUrl = '';
}
if (isUndefined(props.successTarget)) {
props.successTarget = '_self';
}
if (isUndefined(props.frameProps)) {
props.frameProps = {};
}
if (isUndefined(props.webWalletUrl)) {
props.webWalletUrl = '';
}
const newProps = mergeProps(props, PlaygroundAction, ['buttonRounded', 'extraParams', 'timeout']);
const {
autoClose,
action,
buttonText,
buttonColor,
buttonVariant,
buttonSize,
buttonRounded,
children,
disableClose,
title,
scanMessage,
successMessage,
successUrl,
successTarget,
frameProps,
confirmMessage,
extraParams,
timeout,
webWalletUrl,
...rest
} = newProps;
const theme = useTheme();
const browser = useBrowser();
const { api, session } = use(SessionContext);
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [dynamicParams, setDynamicParams] = useState({});
const size = useSize(document.body);
const [success, setSuccess] = useState(false);
const [showFrame, setShowFrame] = useState(success && successUrl && successTarget === 'frame');
const width = size?.width || 0;
// 当打开或关闭组件时,重置部分状态
useEffect(
() => () => {
setSuccess(false);
setShowFrame(false);
},
[open]
);
// If this is just a login button, we do not do anything actually
if (action === 'login') {
if (session.user) {
return (
<Button {...rest} rounded={buttonRounded} color={buttonColor} variant={buttonVariant} size={buttonSize}>
{getMessage(successMessage || `Hello ${session.user.name}`, session)}
</Button>
);
}
return (
<Button
{...rest}
rounded={buttonRounded}
color={buttonColor}
variant={buttonVariant}
size={buttonSize}
onClick={() => session.login()}>
{getMessage(buttonText || title, session)}
</Button>
);
}
const config = actions[action];
if (!actions[action]) {
throw new Error(`Unsupported playground action ${action}`);
}
const doStart = async () => {
if (typeof config.onStart === 'function') {
try {
setLoading(true);
const params = await config.onStart(api, session);
setDynamicParams(params);
setLoading(false);
} catch (err) {
console.error(`Cannot generate dynamicParams for playground action ${getActionName(config, rest)}`);
}
setOpen(true);
} else {
setOpen(true);
}
};
const onStart = async () => {
if (!session.user) {
session.login(doStart);
return;
}
await doStart();
};
const onClose = () => setOpen(false);
const onSuccess = () => {
setSuccess(true);
if (successUrl) {
if (successTarget === 'frame') {
setShowFrame(!!successUrl);
} else if (successTarget === '_blank') {
// 这里是安全的
window.open(successUrl, '_blank');
} else {
// 这里是安全的
window.open(successUrl, '_self');
}
} else if (children) {
// Do nothing
} else if (autoClose) {
setTimeout(onClose, 2000);
}
};
const renderRedirectUrlAfterSuccess = () => (
<>
<Close onClose={onClose} />
<div>
Redirecting to{' '}
<a href={successUrl} target={successTarget}>
{successUrl}
</a>
</div>
</>
);
const renderFrameAfterSuccess = () => (
<>
<Close onClose={onClose} />
<iframe
style={{ width: '100%', height: '100%' }}
allow="fullscreen"
id="successFrame"
title="successFrame"
src={successUrl}
{...frameProps}
/>
</>
);
const showDidConnect = !successUrl || (successUrl && !success);
return (
<>
<Button
{...rest}
rounded={buttonRounded}
color={buttonColor}
variant={buttonVariant}
size={buttonSize}
onClick={onStart}>
{getMessage(buttonText || title, session)} {loading && <CircularProgress size={12} sx={{ color: '#fff' }} />}
</Button>
{open && !showDidConnect && (
<Dialog
open
disableEscapeKeyDown
fullScreen={width < theme.breakpoints.values.sm && !(browser.wallet || browser.arcSphere)}
fullWidth={showFrame}
maxWidth={showFrame ? 'lg' : ''}>
<DialogContent
style={{
padding: success && !showFrame && successUrl ? 55 : 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: showFrame ? theme.breakpoints.values.md : '',
}}>
<Close onClose={onClose} />
{successUrl && success && !showFrame && renderRedirectUrlAfterSuccess()}
{showFrame && renderFrameAfterSuccess()}
</DialogContent>
</Dialog>
)}
<DidConnect
popup
open={open && showDidConnect}
action={getActionName(config, rest)}
checkFn={api.get}
onClose={onClose}
onSuccess={onSuccess}
checkTimeout={timeout}
// 3 layers of extraParams: user props, dynamically generated, from other props
extraParams={Object.assign(getActionParams(config, rest, session), dynamicParams, extraParams)}
webWalletUrl={webWalletUrl}
messages={{
title: getMessage(title, session),
scan: getMessage(scanMessage, session),
confirm: getMessage(confirmMessage, session),
success: children || getMessage(successMessage, session),
}}
/>
</>
);
}
PlaygroundAction.propTypes = {
action: PropTypes.string.isRequired,
autoClose: PropTypes.bool,
buttonText: PropTypes.string,
buttonColor: PropTypes.string,
buttonVariant: PropTypes.string,
buttonSize: PropTypes.string,
buttonRounded: PropTypes.bool,
title: PropTypes.string.isRequired,
scanMessage: PropTypes.string,
successMessage: PropTypes.string,
confirmMessage: PropTypes.string,
extraParams: PropTypes.object,
timeout: PropTypes.number,
successUrl: PropTypes.string,
successTarget: PropTypes.oneOf(['_blank', '_self', 'frame']),
frameProps: PropTypes.object,
webWalletUrl: PropTypes.string,
};
export default PlaygroundAction;