@arcblock/ux 2.10.43 → 2.10.45

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,59 +1,60 @@
1
- import { forwardRef, useState } from 'react';
2
- import PropTypes from 'prop-types';
3
1
  import { getDIDMotifInfo } from '@arcblock/did-motif';
2
+ import { types } from '@ocap/mcrypto';
4
3
  import QRCode from 'qrcode.react';
5
4
  import { Icon } from '@iconify/react';
6
5
  import IconQrCode from '@iconify-icons/material-symbols/qr-code-rounded';
7
- import { Box, Dialog, DialogContent, DialogTitle, Typography } from '@mui/material';
6
+ import { Box, Dialog, DialogContent, Typography } from '@mui/material';
8
7
  import { useCreation, useMemoizedFn } from 'ahooks';
8
+ import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
9
9
 
10
+ import Address, { IDidAddressWrapper } from '../Address';
11
+ import Avatar from '../Avatar';
10
12
  import { temp as colors } from '../Colors';
11
13
  import { DID_PREFIX } from '../Util/constant';
12
- import Address from '../Address';
13
- import Avatar from '../Avatar';
14
14
 
15
- import { isEthereumDid, getFontSize, getDIDColor } from '../Util';
15
+ import { HTMLDidAddressElement } from '../Address/did-address';
16
16
  import { translate } from '../Locale/util';
17
+ import { getDIDColor, isEthereumDid } from '../Util';
17
18
 
18
19
  const translations = {
19
20
  en: {
20
- scanQrcode: 'Scan with DID Wallet to transfer token to here',
21
+ scanQrcode: 'Scan with DID Wallet to view this {role}',
21
22
  download: 'Download',
22
23
  downloadTips: "Don't have DID Wallet ?",
23
24
  },
24
25
  zh: {
25
- scanQrcode: '扫描此二维码,用 DID Wallet 转账',
26
+ scanQrcode: '使用 DID Wallet 扫码查看该 {role}',
26
27
  download: '下载',
27
28
  downloadTips: '没有 DID Wallet ?',
28
29
  },
29
30
  };
30
31
 
31
- const DIDPropTypes = {
32
- did: PropTypes.string.isRequired,
33
- size: PropTypes.number,
34
- component: PropTypes.string,
35
- copyable: PropTypes.bool,
36
- responsive: PropTypes.bool,
37
- showCopyButtonInTooltip: PropTypes.bool,
38
- showAvatar: PropTypes.bool,
39
- showQrcode: PropTypes.bool,
40
- inline: PropTypes.bool,
41
- append: PropTypes.any,
42
- compact: PropTypes.bool,
43
- startChars: PropTypes.number,
44
- endChars: PropTypes.number,
45
- locale: PropTypes.oneOf(['en', 'zh']),
46
- chainId: PropTypes.string,
47
- };
32
+ interface IDIDPropTypes extends IDidAddressWrapper {
33
+ did: string;
34
+ size?: number;
35
+ component?: React.ElementType;
36
+ copyable?: boolean;
37
+ responsive?: boolean;
38
+ showCopyButtonInTooltip?: boolean;
39
+ showAvatar?: boolean;
40
+ showQrcode?: boolean;
41
+ inline?: boolean;
42
+ append?: any;
43
+ compact?: boolean;
44
+ startChars?: number;
45
+ endChars?: number;
46
+ locale?: 'en' | 'zh';
47
+ chainId?: string;
48
+ }
48
49
 
49
50
  const DEFAULT_CHAIN_ID = 'xenon-2020-01-15';
50
51
 
51
- const CHAIN_ID_MAP = {
52
+ const CHAIN_ID_MAP: Record<string, string> = {
52
53
  'main.abtnetwork.io': DEFAULT_CHAIN_ID,
53
54
  'beta.abtnetwork.io': 'beta',
54
55
  };
55
56
 
56
- const getFontColor = (did, didMotifInfo, isEthDid) => {
57
+ const getFontColor = (did: string, didMotifInfo: any, isEthDid: boolean) => {
57
58
  if (isEthDid) {
58
59
  return getDIDColor(did);
59
60
  }
@@ -61,8 +62,8 @@ const getFontColor = (did, didMotifInfo, isEthDid) => {
61
62
  return didMotifInfo.color;
62
63
  };
63
64
 
64
- const isSquareMotif = (roleType) => {
65
- const roles = {
65
+ const isSquareMotif = (roleType: number) => {
66
+ const roles: Record<number, boolean> = {
66
67
  0: true, // ACCOUNT
67
68
  6: true, // ASSET
68
69
  17: true, // TOKEN
@@ -70,7 +71,20 @@ const isSquareMotif = (roleType) => {
70
71
  return !roles[roleType];
71
72
  };
72
73
 
73
- const getAvatarSize = (didMotifInfo, isEthDid, size) => {
74
+ const getRoleName = (roleType: number) => {
75
+ const [roleName] = Object.entries(types.RoleType).find((item) => item[1] === roleType) || [];
76
+
77
+ if (roleName) {
78
+ // UpperCase first word, example:
79
+ // ROLE_ACCOUNT -> Account
80
+ // ROLE_APPLICATION -> Application
81
+ return roleName.toLowerCase().replace(/role_(\S)/g, (_, $1) => $1.toUpperCase());
82
+ }
83
+
84
+ return 'Address';
85
+ };
86
+
87
+ const getAvatarSize = (didMotifInfo: any, isEthDid: boolean, size: number) => {
74
88
  if (isEthDid) {
75
89
  return size * 0.75;
76
90
  }
@@ -80,21 +94,55 @@ const getAvatarSize = (didMotifInfo, isEthDid, size) => {
80
94
  return size;
81
95
  };
82
96
 
83
- /**
84
- * @type React.ForwardRefRenderFunction<HTMLElement, typeof DIDPropTypes>
85
- */
86
- const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest }, ref) => {
97
+ export interface HTMLDIDElement extends HTMLDidAddressElement {
98
+ openQRCode: () => void;
99
+ closeQRCode: () => void;
100
+ }
101
+
102
+ const DID = forwardRef<HTMLDIDElement, IDIDPropTypes>((props, ref) => {
103
+ const {
104
+ did,
105
+ showAvatar = true,
106
+ showQrcode = false,
107
+ locale = 'en',
108
+ size = 0,
109
+ component = 'span',
110
+ copyable = true,
111
+ responsive = true,
112
+ showCopyButtonInTooltip = false,
113
+ inline = false,
114
+ append = null,
115
+ compact = false,
116
+ startChars = 6,
117
+ endChars = 6,
118
+ } = props;
119
+
120
+ const rest = {
121
+ size,
122
+ component,
123
+ copyable,
124
+ responsive,
125
+ showCopyButtonInTooltip,
126
+ inline,
127
+ append,
128
+ compact,
129
+ startChars,
130
+ endChars,
131
+ };
132
+
133
+ const addressRef = useRef<any>(null);
134
+
87
135
  const t = useMemoizedFn((key, data = {}) => {
88
136
  return translate(translations, key, locale, 'en', data);
89
137
  });
90
138
 
139
+ let chainId = props.chainId || '';
91
140
  try {
92
141
  if (!chainId) {
93
- const chainHostUrl = window?.blocklet?.CHAIN_HOST;
142
+ const chainHostUrl = (window as any).blocklet?.CHAIN_HOST;
94
143
  if (chainHostUrl) {
95
144
  const chainHost = new URL(chainHostUrl).hostname;
96
145
  if (chainHost) {
97
- // eslint-disable-next-line no-param-reassign
98
146
  chainId = CHAIN_ID_MAP[chainHost];
99
147
  }
100
148
  }
@@ -105,19 +153,20 @@ const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest
105
153
  const [open, setOpen] = useState(false);
106
154
  const isEthDid = isEthereumDid(did);
107
155
  const didMotifInfo = isEthDid ? undefined : getDIDMotifInfo(did);
108
- const fontSize = getFontSize(rest.size);
109
- const prepend = [
110
- <span key="prefix-did" style={{ flex: '0 0 auto', fontSize, color: getFontColor(did, didMotifInfo, isEthDid) }}>
156
+
157
+ const getPrepend = (avatarSize: number) => [
158
+ <span key="prefix-did" style={{ flex: '0 0 auto', color: getFontColor(did, didMotifInfo, isEthDid) }}>
111
159
  DID:
112
160
  </span>,
113
- <span key="prefix-abt" className="did-address-text" style={{ fontSize }}>
161
+ <span key="prefix-abt" className="did-address-text">
114
162
  ABT:
115
163
  </span>,
116
164
  showAvatar && (
117
165
  <Avatar
118
166
  key="avatar"
167
+ src=""
119
168
  did={did}
120
- size={getAvatarSize(didMotifInfo, isEthDid, rest.size || 18)}
169
+ size={getAvatarSize(didMotifInfo, isEthDid, avatarSize || 18)}
121
170
  style={{ display: 'inline-flex', alignItems: 'center', marginLeft: 2, marginRight: 4 }}
122
171
  blockiesPadding={false}
123
172
  className="did-address-avatar"
@@ -133,8 +182,22 @@ const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest
133
182
  setOpen(true);
134
183
  });
135
184
 
185
+ useImperativeHandle(ref, () => {
186
+ return new Proxy(
187
+ {
188
+ openQRCode: () => setOpen(true),
189
+ closeQRCode: () => setOpen(false),
190
+ },
191
+ {
192
+ get(target: any, key: string) {
193
+ return target[key] || addressRef?.current?.[key];
194
+ },
195
+ }
196
+ );
197
+ });
198
+
136
199
  const downloadUrl = useCreation(() => {
137
- if (['zh', 'en'].includes) {
200
+ if (['zh', 'en'].includes(locale)) {
138
201
  return `https://www.didwallet.io/${locale}`;
139
202
  }
140
203
  return 'https://www.didwallet.io/en';
@@ -165,15 +228,16 @@ const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest
165
228
  return (
166
229
  <>
167
230
  <Address
168
- ref={ref}
169
231
  locale={locale}
170
232
  content={`${DID_PREFIX}${did}`}
171
233
  {...rest}
172
- prepend={prepend}
234
+ ref={addressRef}
235
+ prepend={getPrepend(rest.size)}
173
236
  append={
174
237
  <>
175
238
  {showQrcode ? (
176
239
  <Box
240
+ id="did-qrcode-button"
177
241
  component={Icon}
178
242
  icon={IconQrCode}
179
243
  onClick={openQrCode}
@@ -191,13 +255,21 @@ const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest
191
255
  {did}
192
256
  </Address>
193
257
 
194
- <Dialog open={open} onClose={closeQrCode} maxWidth="sm">
195
- <DialogTitle align="center">
196
- <Typography variant="h6" component="h3">
197
- {t('scanQrcode')}
258
+ <Dialog
259
+ open={open}
260
+ onClose={closeQrCode}
261
+ maxWidth="sm"
262
+ PaperProps={{
263
+ sx: {
264
+ boxShadow: 'none',
265
+ borderRadius: '12px',
266
+ },
267
+ }}>
268
+ {/* @ts-ignore */}
269
+ <DialogContent align="center" sx={{ p: 3 }}>
270
+ <Typography sx={{ mb: 2, fontWeight: 500, fontSize: 18 }}>
271
+ {t('scanQrcode', { role: getRoleName(didMotifInfo?.roleType) })}
198
272
  </Typography>
199
- </DialogTitle>
200
- <DialogContent align="center">
201
273
  <QRCode
202
274
  // eslint-disable-next-line max-len
203
275
  value={`abt://abtwallet.io/i?did=${DID_PREFIX}${did}&action=didRecognize&chainID=${
@@ -207,8 +279,14 @@ const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest
207
279
  renderAs="svg"
208
280
  level="M"
209
281
  />
210
- <Box sx={{ marginTop: 1, textAlign: 'center' }}>
211
- <Address copyable locale={locale} content={`${DID_PREFIX}${did}`} prepend={prepend}>
282
+ <Box sx={{ mt: 1.5, textAlign: 'center' }}>
283
+ <Address
284
+ locale={locale}
285
+ content={`${DID_PREFIX}${did}`}
286
+ prepend={getPrepend(16)}
287
+ {...rest}
288
+ size={16}
289
+ copyable>
212
290
  {did}
213
291
  </Address>
214
292
  </Box>
@@ -228,22 +306,3 @@ const DID = forwardRef(({ did, showAvatar, showQrcode, chainId, locale, ...rest
228
306
  });
229
307
 
230
308
  export default DID;
231
-
232
- DID.propTypes = DIDPropTypes;
233
-
234
- DID.defaultProps = {
235
- size: 0,
236
- component: 'span',
237
- copyable: true,
238
- responsive: true,
239
- showCopyButtonInTooltip: false,
240
- showAvatar: true,
241
- showQrcode: false,
242
- inline: false,
243
- append: null,
244
- compact: false,
245
- startChars: 6,
246
- endChars: 6,
247
- locale: 'en',
248
- chainId: '',
249
- };
package/src/type.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module '@arcblock/did-motif';
@@ -1,220 +0,0 @@
1
- import React, { useRef, useState, forwardRef } from 'react';
2
- import PropTypes from 'prop-types';
3
- import '@fontsource/ubuntu-mono/400.css';
4
- import { green } from '@mui/material/colors';
5
- import { Tooltip, Box } from '@mui/material';
6
- import copy from 'copy-to-clipboard';
7
- import { ContentCopy as CopyIcon, Check as CheckIcon } from '@mui/icons-material';
8
- import noop from 'lodash/noop';
9
-
10
- import { styled } from '../Theme';
11
- import { getFontSize } from '../Util';
12
- import CompactText from './compact-text';
13
-
14
- const translations = {
15
- en: {
16
- copy: 'Click To Copy',
17
- copied: 'Copied!',
18
- },
19
- zh: {
20
- copy: '点击复制',
21
- copied: '已复制!',
22
- },
23
- };
24
-
25
- /**
26
- * DidAddress 组件 (新版设计)
27
- *
28
- * - 样式调整
29
- * - click-to-copy 调整
30
- * - 长文本截断处理 (Ellipsis)
31
- * - 支持 inline 或 block 的显示方式
32
- * - 支持紧凑模式, 该模式下:
33
- * - 占用宽度较小, 因此不考虑水平空间不够用的情况, 且忽略末尾省略号
34
- * - 对于多层元素结构的 children, 保持元素结构, 将最内层 text 替换为 CompactText 组件
35
- * - 为保证 copy 功能正常工作, 原 children 始终渲染, 但在紧凑式下会隐藏
36
- * - 可配合 useMediaQuery 使用
37
- */
38
- const DidAddress = forwardRef(
39
- (
40
- {
41
- component,
42
- size,
43
- copyable,
44
- content,
45
- children,
46
- prepend,
47
- append,
48
- compact,
49
- startChars,
50
- endChars,
51
- locale,
52
- showCopyButtonInTooltip,
53
- ...rest
54
- },
55
- ref
56
- ) => {
57
- if (!translations[locale]) {
58
- // eslint-disable-next-line no-param-reassign
59
- locale = 'en';
60
- }
61
-
62
- const [copied, setCopied] = useState(false);
63
- const textRef = useRef();
64
-
65
- const onCopy = (e) => {
66
- e.stopPropagation();
67
- e.preventDefault();
68
- copy(content || textRef.current.textContent);
69
- setCopied(true);
70
- // 恢复 copied 状态
71
- setTimeout(() => {
72
- setCopied(false);
73
- }, 1500);
74
- };
75
-
76
- let copyElement = null;
77
- if (copyable) {
78
- copyElement = (
79
- <span className="did-address-copy-wrapper" title={copied ? '' : translations[locale].copy}>
80
- {copied ? (
81
- <Tooltip title={translations[locale].copied} placement="bottom" arrow open={copied}>
82
- <CheckIcon className="did-address-copy" style={{ color: green[500] }} />
83
- </Tooltip>
84
- ) : (
85
- /* title prop 直接加在 icon 上不生效 */
86
- <CopyIcon className="did-address-copy" onClick={onCopy} />
87
- )}
88
- </span>
89
- );
90
- }
91
-
92
- return (
93
- <Root as={component} size={size} {...rest} ref={ref}>
94
- {prepend}
95
- <Box sx={{ display: 'none' }} ref={textRef}>
96
- {children}
97
- </Box>
98
- {/* 注意: 该元素必须渲染(可以隐藏), 以便 compact 模式下复制的文本是完整的 */}
99
- <Tooltip title={copyable ? '' : translations[locale].copied} placement="bottom" arrow open={copied}>
100
- {compact ? (
101
- <Box
102
- component="span"
103
- className="did-address-text"
104
- sx={{
105
- cursor: copyable ? 'unset' : 'pointer',
106
- }}
107
- onDoubleClick={copyable ? noop : onCopy}>
108
- <CompactText
109
- startChars={startChars}
110
- endChars={endChars}
111
- showCopyButtonInTooltip={showCopyButtonInTooltip}>
112
- {children}
113
- </CompactText>
114
- </Box>
115
- ) : (
116
- <Box
117
- component="span"
118
- className="did-address-text did-address-truncate"
119
- sx={{
120
- display: compact ? 'none' : 'inline',
121
- cursor: copyable ? 'unset' : 'pointer',
122
- }}
123
- onDoubleClick={copyable ? noop : onCopy}>
124
- {children}
125
- </Box>
126
- )}
127
- </Tooltip>
128
- {copyElement}
129
- {append}
130
- </Root>
131
- );
132
- }
133
- );
134
-
135
- export default DidAddress;
136
-
137
- DidAddress.propTypes = {
138
- component: PropTypes.string,
139
- size: PropTypes.number,
140
- copyable: PropTypes.bool,
141
- // compact mode 下, hover 时会在 tooltip 中显示完整地址, showCopyButtonInTooltip = true 时会在完整地址后显示一个复制按钮
142
- showCopyButtonInTooltip: PropTypes.bool,
143
- children: PropTypes.any,
144
- content: PropTypes.string,
145
- inline: PropTypes.bool,
146
- prepend: PropTypes.any,
147
- append: PropTypes.any,
148
- // 紧凑模式
149
- compact: PropTypes.bool,
150
- startChars: PropTypes.number,
151
- endChars: PropTypes.number,
152
- locale: PropTypes.oneOf(['en', 'zh']),
153
- };
154
-
155
- DidAddress.defaultProps = {
156
- component: 'span',
157
- size: 0,
158
- copyable: true,
159
- showCopyButtonInTooltip: false,
160
- children: null,
161
- content: '',
162
- inline: false,
163
- prepend: null,
164
- append: null,
165
- compact: false,
166
- startChars: 6,
167
- endChars: 6,
168
- locale: 'en',
169
- };
170
-
171
- const Root = styled(Box, { shouldForwardProp: (prop) => prop !== 'inline' })`
172
- font-family: 'Ubuntu Mono', monospace;
173
- && {
174
- display: ${({ inline }) => (inline ? 'inline-flex' : 'flex')};
175
- align-items: center;
176
- max-width: 100%;
177
- overflow: hidden;
178
- color: #ccc;
179
- font-size: ${(props) => getFontSize(props.size)};
180
- font-weight: 400;
181
-
182
- svg {
183
- fill: currentColor;
184
- }
185
- }
186
-
187
- .did-address-text {
188
- color: #666;
189
- }
190
- /* truncate string with ellipsis */
191
- .did-address-truncate {
192
- white-space: nowrap;
193
- overflow: hidden;
194
- text-overflow: ellipsis;
195
- }
196
-
197
- .did-address-copy-wrapper {
198
- display: flex;
199
- justify-content: center;
200
- align-items: center;
201
- width: 1em;
202
- height: 1em;
203
- margin-left: 8px;
204
- }
205
- .did-address-copy {
206
- flex: 0 0 auto;
207
- font-size: 1em;
208
- color: #999;
209
- cursor: pointer;
210
- }
211
-
212
- /* link */
213
- a {
214
- color: #666;
215
- }
216
- &:hover a {
217
- color: #222;
218
- text-decoration: underline;
219
- }
220
- `;
@@ -1,18 +0,0 @@
1
- import PropTypes from 'prop-types';
2
- import DidAddress from './did-address';
3
- import ResponsiveDidAddress from './responsive-did-address';
4
-
5
- export default function DidAddressWrapper({ responsive, ...rest }) {
6
- if (responsive) {
7
- return <ResponsiveDidAddress {...rest} />;
8
- }
9
- return <DidAddress {...rest} />;
10
- }
11
-
12
- DidAddressWrapper.propTypes = {
13
- responsive: PropTypes.bool,
14
- };
15
-
16
- DidAddressWrapper.defaultProps = {
17
- responsive: true,
18
- };
@@ -1,79 +0,0 @@
1
- import { useState, createRef, useEffect, useRef } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { useSize } from 'ahooks';
4
- import { styled } from '../Theme';
5
-
6
- import DidAddress from './did-address';
7
-
8
- /**
9
- * 根据父容器宽度自动切换 compact 模式
10
- *
11
- * 实现逻辑:
12
- * - DidAddress 外层包裹一个容器, 其宽度自动撑满父容器宽度 (即这个容器需要是块级元素或 100% 宽的 inline-block)
13
- * - DidAddress 本身以 inline 形式渲染 (方便探测 did-address 的 full-width)
14
- * - 组件 mounted 时记录 did address 的 full-width (非 compact 模式的宽度)
15
- * - 监听容器宽度变化, 当容器宽度变化时, 对比容器宽度和 did address full-width, => 切换 compact 模式
16
- * - TODO: 初始化时, 在确定是否应该以 compact 模式渲染前, 隐藏显示, 避免闪烁问题
17
- */
18
- export default function ResponsiveDidAddress({ style, className, component, ...rest }) {
19
- const [compact, setCompact] = useState(false);
20
- // did address 完整显示时的宽度
21
- const [addressFullWidth, setAddressFullWidth] = useState(null);
22
- const containerRef = useRef(null);
23
- const size = useSize(containerRef);
24
- const containerWidth = size?.width || 0;
25
- const ref = createRef();
26
- // 存储完整显示时 address 组件的宽度
27
- useEffect(() => {
28
- if (!compact && addressFullWidth === null) {
29
- setAddressFullWidth(ref.current.offsetWidth);
30
- }
31
- // eslint-disable-next-line react-hooks/exhaustive-deps
32
- }, []);
33
-
34
- useEffect(() => {
35
- if (containerWidth && addressFullWidth) {
36
- setCompact(containerWidth < addressFullWidth);
37
- }
38
- }, [containerWidth, addressFullWidth]);
39
- return (
40
- <Root as={component} ref={containerRef} style={style} className={className}>
41
- <StyledDidAddress {...rest} component={component} inline compact={compact} ref={ref} />
42
- </Root>
43
- );
44
- }
45
-
46
- ResponsiveDidAddress.propTypes = {
47
- style: PropTypes.object,
48
- className: PropTypes.string,
49
- component: PropTypes.string,
50
- };
51
-
52
- ResponsiveDidAddress.defaultProps = {
53
- style: {},
54
- className: '',
55
- component: 'span',
56
- };
57
-
58
- const Root = styled('div')`
59
- display: block;
60
- overflow: hidden;
61
- ${({ inline }) =>
62
- inline &&
63
- `
64
- display: inline-block;
65
- width: 100%;
66
- `}
67
- `;
68
-
69
- const StyledDidAddress = styled(DidAddress)`
70
- && {
71
- max-width: none;
72
- }
73
- .did-address-text {
74
- /* 禁止文本 Ellipsis/截断, 以便测量真实的宽度 */
75
- white-space: nowrap;
76
- overflow: visible;
77
- text-overflow: unset;
78
- }
79
- `;