@blocklet/ui-react 2.9.29 → 2.9.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.9.29",
3
+ "version": "2.9.31",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -63,8 +63,8 @@
63
63
  },
64
64
  "dependencies": {
65
65
  "@abtnode/constant": "1.16.23",
66
- "@arcblock/did-connect": "^2.9.29",
67
- "@arcblock/ux": "^2.9.29",
66
+ "@arcblock/did-connect": "^2.9.31",
67
+ "@arcblock/ux": "^2.9.31",
68
68
  "@blocklet/js-sdk": "1.16.23",
69
69
  "@emotion/react": "^11.10.4",
70
70
  "@emotion/styled": "^11.10.4",
@@ -99,5 +99,5 @@
99
99
  "jest": "^28.1.3",
100
100
  "unbuild": "^2.0.0"
101
101
  },
102
- "gitHead": "fff3168d6a3c902a39818dfa8eb42544b88225e9"
102
+ "gitHead": "4430dbf5949032024b28b129687d28e21a1155fc"
103
103
  }
@@ -1,15 +1,17 @@
1
- import { Button, Box, ClickAwayListener, Fade, Paper } from '@mui/material';
2
- import { Icon } from '@iconify/react';
3
- import { temp as colors } from '@arcblock/ux/lib/Colors';
4
1
  import { SessionContext } from '@arcblock/did-connect/lib/Session';
5
- import { useContext } from 'react';
2
+ import { temp as colors } from '@arcblock/ux/lib/Colors';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import { translate } from '@arcblock/ux/lib/Locale/util';
6
5
  import SessionPermission from '@arcblock/ux/lib/SessionPermission';
7
- import PropTypes from 'prop-types';
6
+ import { Icon } from '@iconify/react';
7
+ import CloseIcon from '@mui/icons-material/Close';
8
+ import { Box, ClickAwayListener, Fade, IconButton, Paper } from '@mui/material';
8
9
  import { useMemoizedFn } from 'ahooks';
9
- import { translate } from '@arcblock/ux/lib/Locale/util';
10
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
11
- import useComponentInstalled from './use-component-installed';
10
+ import PropTypes from 'prop-types';
11
+ import { useContext } from 'react';
12
+ import InstallerItem from './installer-item';
12
13
  import translations from './locales';
14
+ import useComponentInstalled from './use-component-installed';
13
15
 
14
16
  function ComponentInstaller({
15
17
  warnIcon,
@@ -28,32 +30,17 @@ function ComponentInstaller({
28
30
  const t = useMemoizedFn((key, data = {}) => {
29
31
  return translate(translations, key, locale, 'en', data);
30
32
  });
31
- const { installed, optionalComponent, installUrl, storeUrl, installStatus, installStatusDone, definedInBlockletYML } =
32
- useComponentInstalled({
33
- did,
34
- onInstalled,
35
- onError,
36
- });
33
+ const { installed, optComponents, installStatus, definedInBlockletYML } = useComponentInstalled({
34
+ did,
35
+ onInstalled,
36
+ onError,
37
+ });
37
38
  const sessionCtx = useContext(SessionContext);
38
39
 
39
- const handleInstall = () => {
40
- window.open(installUrl, '_blank');
41
- };
42
-
43
40
  const handleClose = () => {
44
41
  onClose?.(false);
45
42
  };
46
43
 
47
- const handleOpenStore = () => {
48
- window.open(storeUrl, '_blank');
49
- };
50
-
51
- const handleRefresh = () => {
52
- window.location.reload();
53
- };
54
-
55
- const size = 60;
56
-
57
44
  if (disabled) {
58
45
  return children;
59
46
  }
@@ -73,11 +60,8 @@ function ComponentInstaller({
73
60
  {fallback}
74
61
  {children({
75
62
  hasPermission,
76
- optionalComponent,
63
+ optComponents,
77
64
  installStatus,
78
- handleOpenStore,
79
- handleInstall,
80
- handleRefresh,
81
65
  })}
82
66
  </>
83
67
  );
@@ -104,7 +88,7 @@ function ComponentInstaller({
104
88
  zIndex: 3000,
105
89
  borderRadius: 3,
106
90
  width: 400,
107
- maxWidth: 400,
91
+ maxWidth: '90vw',
108
92
  borderColor: colors.lineStep,
109
93
  border: '0 !important',
110
94
  fontSize: '14px',
@@ -118,6 +102,7 @@ function ComponentInstaller({
118
102
  padding: '20px 24px',
119
103
  marginLeft: 0,
120
104
  display: 'flex',
105
+ alignItems: 'center',
121
106
  flexDirection: 'row',
122
107
  justifyContent: 'flex-start',
123
108
  }}>
@@ -125,18 +110,17 @@ function ComponentInstaller({
125
110
  <Box sx={{ marginLeft: 1, fontSize: '16px', fontWeight: 'bold' }}>
126
111
  {t('componentInstallerTitle')}
127
112
  </Box>
113
+ <Box sx={{ flex: 1 }} />
114
+ {onClose ? (
115
+ <IconButton variant="outlined" className="button" onClick={handleClose}>
116
+ <CloseIcon />
117
+ </IconButton>
118
+ ) : null}
128
119
  </Box>
129
120
  <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />
130
121
  <Box sx={{ padding: '20px 24px', marginTop: 0 }}>
131
122
  {t('componentInstallerNoDefinedInBlockletYML')}: {did}
132
123
  </Box>
133
- <Box sx={{ padding: '0px 24px' }}>
134
- {onClose ? (
135
- <Button sx={{ marginBottom: 2 }} variant="outlined" className="button" onClick={handleClose}>
136
- {t('componentInstallerClose')}
137
- </Button>
138
- ) : null}
139
- </Box>
140
124
  </Box>
141
125
  ) : (
142
126
  <Box sx={{ display: 'flex', flexDirection: 'column' }}>
@@ -146,128 +130,40 @@ function ComponentInstaller({
146
130
  marginLeft: 0,
147
131
  display: 'flex',
148
132
  flexDirection: 'row',
133
+ alignItems: 'center',
149
134
  justifyContent: 'flex-start',
150
135
  }}>
151
136
  {warnIcon || <Icon icon="mdi:warning-box" style={{ color: 'yellowgreen', fontSize: 24 }} />}
152
137
  <Box sx={{ marginLeft: 1, fontSize: '16px', fontWeight: 'bold' }}>
153
138
  {t('componentInstallerTitle')}
154
139
  </Box>
140
+ <Box sx={{ flex: 1 }} />
141
+ {onClose ? (
142
+ <IconButton variant="outlined" className="button" onClick={handleClose}>
143
+ <CloseIcon />
144
+ </IconButton>
145
+ ) : null}
155
146
  </Box>
156
147
  <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />
157
- <Box
158
- sx={{
159
- padding: '20px 24px',
160
- paddingTop: 0.5,
161
- marginTop: 2,
162
- display: 'flex',
163
- flexDirection: 'row',
164
- justifyContent: 'start',
165
- alignItems: 'flex-start',
166
- }}>
167
- <img
168
- style={{ width: size, height: size, minWidth: size, minHeight: size }}
169
- src={optionalComponent.logoUrl}
170
- alt={optionalComponent.meta.title}
171
- />
172
- <Box
173
- sx={{
174
- display: 'flex',
175
- flexDirection: 'column',
176
- justifyContent: 'start',
177
- alignItems: 'start',
178
- marginLeft: 2,
179
- }}>
180
- <Box
181
- sx={{
182
- fontSize: '16px',
183
- fontWeight: 'bold',
184
- cursor: 'pointer',
185
- '.link-icon': {
186
- opacity: 0,
187
- },
188
- ':hover .link-icon': {
189
- opacity: 1,
190
- },
191
- }}
192
- onClick={handleOpenStore}>
193
- {optionalComponent.meta.title}
194
- <Box
195
- sx={{
196
- paddingLeft: 1,
197
- fontSize: '13px',
198
- fontWeight: '400',
199
- }}
200
- component="span">
201
- {optionalComponent.meta.version}
202
- <Icon
203
- className="link-icon"
204
- icon="fluent:open-20-filled"
205
- style={{
206
- color: colors.primaryBase,
207
- fontSize: 16,
208
- transform: 'translate(6px, 3px)',
209
- transition: 'all 0.3s',
210
- }}
211
- />
212
- </Box>
213
- </Box>
214
- <Box sx={{ marginTop: 0, opacity: 0.7 }}>{optionalComponent.meta.description}</Box>
215
- <Box sx={{ display: hasPermission ? 'flex' : 'none', flexDirection: 'row', gap: 1 }}>
216
- {installStatus ? (
217
- <Box sx={{ marginTop: 2, opacity: 0.7 }}>
218
- {installStatusDone ? (
219
- <Button key="refresh" variant="contained" onClick={handleRefresh}>
220
- {t('componentInstallerRefresh')}
221
- </Button>
222
- ) : (
223
- <Button
224
- key="status"
225
- disabled
226
- sx={{ color: '#333' }}
227
- startIcon={
228
- <Icon icon="line-md:loading-loop" style={{ color: '#333', fontSize: 16 }} />
229
- }
230
- variant="contained">
231
- {installStatus}
232
- </Button>
233
- )}
234
- </Box>
235
- ) : (
236
- <Button
237
- key="install"
238
- sx={{ marginTop: 2 }}
239
- variant="contained"
240
- className="button"
241
- onClick={handleInstall}>
242
- {t('componentInstallerInstall')}
243
- </Button>
244
- )}
245
- {onClose ? (
246
- <Button sx={{ marginTop: 2 }} variant="outlined" className="button" onClick={handleClose}>
247
- {t('componentInstallerClose')}
248
- </Button>
249
- ) : null}
250
- </Box>
251
- {installStatusDone ? (
252
- <Box sx={{ marginTop: 2, opacity: 0.7 }}>{t('componentInstallerSuccessInstalled')}</Box>
253
- ) : null}
254
- </Box>
148
+ <Box sx={{ maxHeight: '70vh', overflowY: 'auto' }}>
149
+ {optComponents.map((optionalComponent, index) => {
150
+ return (
151
+ <InstallerItem
152
+ t={t}
153
+ key={optionalComponent.meta?.did || index}
154
+ hasPermission={hasPermission}
155
+ index={index}
156
+ optionalComponent={optionalComponent}
157
+ installStatus={installStatus[optionalComponent.meta?.did]}
158
+ />
159
+ );
160
+ })}
255
161
  </Box>
256
-
257
162
  {hasPermission ? null : (
258
163
  <>
259
164
  <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />
260
165
  <Box sx={{ padding: '20px 24px' }}>
261
166
  <Box sx={{ opacity: 1 }}>{t('componentInstallerSuggestions')}</Box>
262
- {onClose ? (
263
- <Button
264
- sx={{ marginTop: 2, alignSelf: 'flex-start' }}
265
- variant="outlined"
266
- className="button"
267
- onClick={handleClose}>
268
- {t('componentInstallerClose')}
269
- </Button>
270
- ) : null}
271
167
  </Box>
272
168
  </>
273
169
  )}
@@ -286,7 +182,7 @@ function ComponentInstaller({
286
182
  ComponentInstaller.propTypes = {
287
183
  disabled: PropTypes.bool,
288
184
  warnIcon: PropTypes.node,
289
- did: PropTypes.string.isRequired,
185
+ did: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).isRequired,
290
186
  noPermissionMute: PropTypes.bool,
291
187
  onInstalled: PropTypes.func,
292
188
  onError: PropTypes.func,
@@ -318,5 +214,10 @@ export default function WrapComponentInstaller(props) {
318
214
  }
319
215
 
320
216
  WrapComponentInstaller.propTypes = {
217
+ ...ComponentInstaller.propTypes,
321
218
  children: PropTypes.any.isRequired,
322
219
  };
220
+
221
+ WrapComponentInstaller.defaultProps = {
222
+ ...ComponentInstaller.defaultProps,
223
+ };
@@ -0,0 +1,131 @@
1
+ import { temp as colors } from '@arcblock/ux/lib/Colors';
2
+ import { Icon } from '@iconify/react';
3
+ import { Box, Button } from '@mui/material';
4
+ import PropTypes from 'prop-types';
5
+
6
+ export default function InstallerItem({ optionalComponent, index, installStatus, hasPermission, t }) {
7
+ const handleInstall = () => {
8
+ window.open(optionalComponent?.installUrl, '_blank');
9
+ };
10
+
11
+ const handleOpenStore = () => {
12
+ window.open(optionalComponent?.storeUrl, '_blank');
13
+ };
14
+
15
+ const handleRefresh = () => {
16
+ window.location.reload();
17
+ };
18
+
19
+ const installStatusDone = installStatus === 'stopped' || installStatus === 'running';
20
+ const size = 60;
21
+
22
+ return (
23
+ <Box>
24
+ {index === 0 ? null : <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />}
25
+ <Box
26
+ sx={{
27
+ padding: '20px 24px',
28
+ paddingTop: 0.5,
29
+ marginTop: 2,
30
+ display: 'flex',
31
+ flexDirection: 'row',
32
+ justifyContent: 'start',
33
+ alignItems: 'flex-start',
34
+ }}>
35
+ <img
36
+ style={{ width: size, height: size, minWidth: size, minHeight: size }}
37
+ src={optionalComponent.logoUrl}
38
+ alt={optionalComponent.meta.title}
39
+ />
40
+ <Box
41
+ sx={{
42
+ display: 'flex',
43
+ flexDirection: 'column',
44
+ justifyContent: 'start',
45
+ alignItems: 'start',
46
+ marginLeft: 2,
47
+ }}>
48
+ <Box
49
+ sx={{
50
+ fontSize: '16px',
51
+ fontWeight: 'bold',
52
+ cursor: 'pointer',
53
+ '.link-icon': {
54
+ opacity: 0,
55
+ },
56
+ ':hover .link-icon': {
57
+ opacity: 1,
58
+ },
59
+ }}
60
+ onClick={() => handleOpenStore(optionalComponent.meta?.did)}>
61
+ {optionalComponent.meta.title}
62
+ <Box
63
+ sx={{
64
+ paddingLeft: 1,
65
+ fontSize: '13px',
66
+ fontWeight: '400',
67
+ }}
68
+ component="span">
69
+ {optionalComponent.meta.version}
70
+ <Icon
71
+ className="link-icon"
72
+ icon="fluent:open-20-filled"
73
+ style={{
74
+ color: colors.primaryBase,
75
+ fontSize: 16,
76
+ transform: 'translate(6px, 3px)',
77
+ transition: 'all 0.3s',
78
+ }}
79
+ />
80
+ </Box>
81
+ </Box>
82
+ <Box sx={{ marginTop: 0, opacity: 0.7 }}>{optionalComponent.meta.description}</Box>
83
+ <Box sx={{ display: hasPermission ? 'flex' : 'none', flexDirection: 'row', gap: 1 }}>
84
+ {installStatus ? (
85
+ <Box sx={{ marginTop: 2, opacity: 0.7 }}>
86
+ {installStatusDone ? (
87
+ <Button key="refresh" variant="contained" onClick={handleRefresh}>
88
+ {t('componentInstallerRefresh')}
89
+ </Button>
90
+ ) : (
91
+ <Button
92
+ key="status"
93
+ disabled
94
+ sx={{ color: '#333' }}
95
+ startIcon={<Icon icon="line-md:loading-loop" style={{ color: '#333', fontSize: 16 }} />}
96
+ variant="contained">
97
+ {installStatus}
98
+ </Button>
99
+ )}
100
+ </Box>
101
+ ) : (
102
+ <Button
103
+ key="install"
104
+ sx={{ marginTop: 2 }}
105
+ variant="contained"
106
+ className="button"
107
+ onClick={() => handleInstall(optionalComponent.meta?.did)}>
108
+ {t('componentInstallerInstall')}
109
+ </Button>
110
+ )}
111
+ </Box>
112
+ {installStatusDone ? (
113
+ <Box sx={{ marginTop: 2, opacity: 0.7 }}>{t('componentInstallerSuccessInstalled')}</Box>
114
+ ) : null}
115
+ </Box>
116
+ </Box>
117
+ </Box>
118
+ );
119
+ }
120
+
121
+ InstallerItem.propTypes = {
122
+ t: PropTypes.func.isRequired,
123
+ optionalComponent: PropTypes.object.isRequired,
124
+ index: PropTypes.number.isRequired,
125
+ installStatus: PropTypes.string,
126
+ hasPermission: PropTypes.bool.isRequired,
127
+ };
128
+
129
+ InstallerItem.defaultProps = {
130
+ installStatus: '',
131
+ };
@@ -1,32 +1,48 @@
1
1
  import { AUTH_SERVICE_PREFIX } from '@arcblock/did-connect/lib/constant';
2
- import { useMemo, useRef, useState, useEffect } from 'react';
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import urlJoin from 'url-join';
4
4
 
5
+ const parseDidToSet = (did) => {
6
+ if (typeof did === 'string') {
7
+ return new Set(did.split(';;'));
8
+ }
9
+ return new Set(did);
10
+ };
11
+
5
12
  function useComponentInstalled({ did, onInstalled, onError }) {
6
- const [installStatus, setInstallStatus] = useState('');
13
+ const didKeys = Array.isArray(did) ? did.join(';;') : did;
14
+ const [installStatus, setInstallStatus] = useState({});
7
15
  const onInstalledRef = useRef({ onInstalled, onError });
8
16
  onInstalledRef.current = { onInstalled, onError };
9
17
 
10
18
  const { optionalComponents, componentMountPoints } = window.blocklet;
11
19
 
12
- const optionalComponent = useMemo(() => {
20
+ const optComponents = useMemo(() => {
13
21
  if (!optionalComponents || !optionalComponents.length) {
14
22
  return null;
15
23
  }
16
- const component = optionalComponents.find((c) => c.meta.did === did);
17
- (component ? onInstalledRef.current.onError : onInstalledRef.current.onInstalled)?.(component);
18
- return component;
19
- }, [did, optionalComponents]);
24
+ const didSet = parseDidToSet(didKeys);
25
+ const components = optionalComponents.filter((c) => didSet.has(c.meta.did));
26
+ (components ? onInstalledRef.current.onError : onInstalledRef.current.onInstalled)?.(components);
27
+ return components;
28
+ }, [didKeys, optionalComponents]);
20
29
 
21
30
  const definedInBlockletYML = useMemo(() => {
22
- if (optionalComponent) {
31
+ if (optComponents.length) {
23
32
  return true;
24
33
  }
25
- return (componentMountPoints || []).find((item) => item.did === did);
26
- }, [optionalComponent, componentMountPoints, did]);
34
+ const didSet = parseDidToSet(didKeys);
35
+ return (componentMountPoints || []).find((item) => didSet.has(item.did));
36
+ }, [optComponents, componentMountPoints, didKeys]);
27
37
 
28
- const installUrl = urlJoin(window.blocklet.appUrl, AUTH_SERVICE_PREFIX, `/admin/components?install-component=${did}`);
29
- const storeUrl = optionalComponent ? urlJoin(optionalComponent.meta.homepage, 'blocklets', did) : '';
38
+ optComponents.forEach((item) => {
39
+ item.storeUrl = urlJoin(item.meta.homepage, 'blocklets', item.meta.did);
40
+ item.installUrl = urlJoin(
41
+ window.blocklet.appUrl,
42
+ AUTH_SERVICE_PREFIX,
43
+ `/admin/components?install-component=${item.meta.did}`
44
+ );
45
+ });
30
46
 
31
47
  useEffect(() => {
32
48
  const handle = (event) => {
@@ -36,33 +52,35 @@ function useComponentInstalled({ did, onInstalled, onError }) {
36
52
 
37
53
  if (event.data?.kind === 'component-installer' && event.data?.blocklet?.children) {
38
54
  let hasChild = false;
55
+ const didSet = parseDidToSet(didKeys);
39
56
  event.data?.blocklet?.children.forEach((item) => {
40
- if (item.meta?.did === did) {
57
+ if (didSet.has(item.meta?.did)) {
41
58
  hasChild = true;
42
- setInstallStatus(item.status || 'waiting');
59
+ setInstallStatus((value) => {
60
+ return {
61
+ ...value,
62
+ [item.meta?.did]: item.status || 'waiting',
63
+ };
64
+ });
43
65
  }
44
66
  });
45
67
  if (!hasChild) {
46
- setInstallStatus('');
68
+ setInstallStatus({});
47
69
  }
48
70
  }
49
71
  };
72
+
50
73
  window.addEventListener('message', handle);
51
74
  return () => {
52
75
  window.removeEventListener('message', handle);
53
76
  };
54
- }, [did]);
55
-
56
- const installStatusDone = installStatus === 'stopped' || installStatus === 'running';
77
+ }, [didKeys]);
57
78
 
58
79
  return {
59
- optionalComponent,
60
- installed: !optionalComponent && definedInBlockletYML,
61
- installUrl,
62
- storeUrl,
80
+ optComponents,
81
+ installed: !optComponents.length && definedInBlockletYML,
63
82
  installStatus,
64
83
  setInstallStatus,
65
- installStatusDone,
66
84
  definedInBlockletYML,
67
85
  };
68
86
  }
@@ -206,7 +206,11 @@ export default function UserCenter({
206
206
  const formatError = {
207
207
  message: errorMessage,
208
208
  };
209
- return <ErrorFallback error={formatError} />;
209
+ return (
210
+ <Box sx={{ width: '100%' }}>
211
+ <ErrorFallback error={formatError} />
212
+ </Box>
213
+ );
210
214
  }
211
215
 
212
216
  return (