@blocklet/ui-react 2.9.14 → 2.9.16

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.
@@ -0,0 +1,305 @@
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
+ import { SessionContext } from '@arcblock/did-connect/lib/Session';
5
+ import { useContext } from 'react';
6
+ import SessionPermission from '@arcblock/ux/lib/SessionPermission';
7
+ import PropTypes from 'prop-types';
8
+ 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';
12
+ import translations from './locales';
13
+
14
+ function ComponentInstaller({
15
+ warnIcon,
16
+ did,
17
+ noPermissionMute,
18
+ onInstalled,
19
+ onError,
20
+ children,
21
+ closeByOutSize,
22
+ onClose,
23
+ fallback,
24
+ roles = ['owner', 'admin'],
25
+ }) {
26
+ const { locale } = useLocaleContext();
27
+ const t = useMemoizedFn((key, data = {}) => {
28
+ return translate(translations, key, locale, 'en', data);
29
+ });
30
+ const { installed, optionalComponent, installUrl, storeUrl, installStatus, installStatusDone, definedInBlockletYML } =
31
+ useComponentInstalled({
32
+ did,
33
+ onInstalled,
34
+ onError,
35
+ });
36
+ const sessionCtx = useContext(SessionContext);
37
+
38
+ const handleInstall = () => {
39
+ window.open(installUrl, '_blank');
40
+ };
41
+
42
+ const handleClose = () => {
43
+ onClose?.(false);
44
+ };
45
+
46
+ const handleOpenStore = () => {
47
+ window.open(storeUrl, '_blank');
48
+ };
49
+
50
+ const handleRefresh = () => {
51
+ window.location.reload();
52
+ };
53
+
54
+ const size = 60;
55
+
56
+ return (
57
+ <SessionPermission session={sessionCtx?.session} roles={roles}>
58
+ {({ hasPermission }) => {
59
+ if (installed) {
60
+ return children;
61
+ }
62
+ if (noPermissionMute && !hasPermission) {
63
+ return fallback || null;
64
+ }
65
+ if (typeof children === 'function') {
66
+ return (
67
+ <>
68
+ {fallback}
69
+ {children({
70
+ hasPermission,
71
+ optionalComponent,
72
+ installStatus,
73
+ handleOpenStore,
74
+ handleInstall,
75
+ handleRefresh,
76
+ })}
77
+ </>
78
+ );
79
+ }
80
+ // not installed, but has permission, can install directly
81
+ return (
82
+ <>
83
+ {fallback}
84
+ <ClickAwayListener
85
+ onClickAway={(e) => {
86
+ e.preventDefault();
87
+ e.stopPropagation();
88
+ if (closeByOutSize) {
89
+ handleClose();
90
+ }
91
+ }}>
92
+ <Fade in timeout={350}>
93
+ <Paper
94
+ variant="outlined"
95
+ sx={{
96
+ position: 'fixed',
97
+ top: 20,
98
+ right: 20,
99
+ zIndex: 3000,
100
+ borderRadius: 3,
101
+ width: 400,
102
+ maxWidth: 400,
103
+ borderColor: colors.lineStep,
104
+ border: '0 !important',
105
+ fontSize: '14px',
106
+ textAlign: 'left',
107
+ boxShadow: `0px 8px 16px 0px ${colors.gray6}, 0px 0px 0px 1px ${colors.gray6}`,
108
+ }}>
109
+ {!definedInBlockletYML ? (
110
+ <Box sx={{ display: 'flex', flexDirection: 'column' }}>
111
+ <Box
112
+ sx={{
113
+ padding: '20px 24px',
114
+ marginLeft: 0,
115
+ display: 'flex',
116
+ flexDirection: 'row',
117
+ justifyContent: 'flex-start',
118
+ }}>
119
+ {warnIcon || <Icon icon="mdi:warning-box" style={{ color: 'yellowgreen', fontSize: 24 }} />}
120
+ <Box sx={{ marginLeft: 1, fontSize: '16px', fontWeight: 'bold' }}>
121
+ {t('componentInstallerTitle')}
122
+ </Box>
123
+ </Box>
124
+ <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />
125
+ <Box sx={{ padding: '20px 24px', marginTop: 0 }}>
126
+ {t('componentInstallerNoDefinedInBlockletYML')}: {did}
127
+ </Box>
128
+ <Box sx={{ padding: '0px 24px' }}>
129
+ {onClose ? (
130
+ <Button sx={{ marginBottom: 2 }} variant="outlined" className="button" onClick={handleClose}>
131
+ {t('componentInstallerClose')}
132
+ </Button>
133
+ ) : null}
134
+ </Box>
135
+ </Box>
136
+ ) : (
137
+ <Box sx={{ display: 'flex', flexDirection: 'column' }}>
138
+ <Box
139
+ sx={{
140
+ padding: '20px 24px',
141
+ marginLeft: 0,
142
+ display: 'flex',
143
+ flexDirection: 'row',
144
+ justifyContent: 'flex-start',
145
+ }}>
146
+ {warnIcon || <Icon icon="mdi:warning-box" style={{ color: 'yellowgreen', fontSize: 24 }} />}
147
+ <Box sx={{ marginLeft: 1, fontSize: '16px', fontWeight: 'bold' }}>
148
+ {t('componentInstallerTitle')}
149
+ </Box>
150
+ </Box>
151
+ <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />
152
+ <Box
153
+ sx={{
154
+ padding: '20px 24px',
155
+ paddingTop: 0.5,
156
+ marginTop: 2,
157
+ display: 'flex',
158
+ flexDirection: 'row',
159
+ justifyContent: 'start',
160
+ alignItems: 'flex-start',
161
+ }}>
162
+ <img
163
+ style={{ width: size, height: size, minWidth: size, minHeight: size }}
164
+ src={optionalComponent.logoUrl}
165
+ alt={optionalComponent.meta.title}
166
+ />
167
+ <Box
168
+ sx={{
169
+ display: 'flex',
170
+ flexDirection: 'column',
171
+ justifyContent: 'start',
172
+ alignItems: 'start',
173
+ marginLeft: 2,
174
+ }}>
175
+ <Box
176
+ sx={{
177
+ fontSize: '16px',
178
+ fontWeight: 'bold',
179
+ cursor: 'pointer',
180
+ '.link-icon': {
181
+ opacity: 0,
182
+ },
183
+ ':hover .link-icon': {
184
+ opacity: 1,
185
+ },
186
+ }}
187
+ onClick={handleOpenStore}>
188
+ {optionalComponent.meta.title}
189
+ <Box
190
+ sx={{
191
+ paddingLeft: 1,
192
+ fontSize: '13px',
193
+ fontWeight: '400',
194
+ }}
195
+ component="span">
196
+ {optionalComponent.meta.version}
197
+ <Icon
198
+ className="link-icon"
199
+ icon="fluent:open-20-filled"
200
+ style={{
201
+ color: colors.primaryBase,
202
+ fontSize: 16,
203
+ transform: 'translate(6px, 3px)',
204
+ transition: 'all 0.3s',
205
+ }}
206
+ />
207
+ </Box>
208
+ </Box>
209
+ <Box sx={{ marginTop: 0, opacity: 0.7 }}>{optionalComponent.meta.description}</Box>
210
+ <Box sx={{ display: hasPermission ? 'flex' : 'none', flexDirection: 'row', gap: 1 }}>
211
+ {installStatus ? (
212
+ <Box sx={{ marginTop: 2, opacity: 0.7 }}>
213
+ {installStatusDone ? (
214
+ <Button key="refresh" variant="contained" onClick={handleRefresh}>
215
+ {t('componentInstallerRefresh')}
216
+ </Button>
217
+ ) : (
218
+ <Button
219
+ key="status"
220
+ disabled
221
+ sx={{ color: '#333' }}
222
+ startIcon={
223
+ <Icon icon="line-md:loading-loop" style={{ color: '#333', fontSize: 16 }} />
224
+ }
225
+ variant="contained">
226
+ {installStatus}
227
+ </Button>
228
+ )}
229
+ </Box>
230
+ ) : (
231
+ <Button
232
+ key="install"
233
+ sx={{ marginTop: 2 }}
234
+ variant="contained"
235
+ className="button"
236
+ onClick={handleInstall}>
237
+ {t('componentInstallerInstall')}
238
+ </Button>
239
+ )}
240
+ {onClose ? (
241
+ <Button sx={{ marginTop: 2 }} variant="outlined" className="button" onClick={handleClose}>
242
+ {t('componentInstallerClose')}
243
+ </Button>
244
+ ) : null}
245
+ </Box>
246
+ {installStatusDone ? (
247
+ <Box sx={{ marginTop: 2, opacity: 0.7 }}>{t('componentInstallerSuccessInstalled')}</Box>
248
+ ) : null}
249
+ </Box>
250
+ </Box>
251
+
252
+ {hasPermission ? null : (
253
+ <>
254
+ <Box sx={{ width: '100%', height: '1px', backgroundColor: colors.gray6 }} />
255
+ <Box sx={{ padding: '20px 24px' }}>
256
+ <Box sx={{ opacity: 1 }}>{t('componentInstallerSuggestions')}</Box>
257
+ {onClose ? (
258
+ <Button
259
+ sx={{ marginTop: 2, alignSelf: 'flex-start' }}
260
+ variant="outlined"
261
+ className="button"
262
+ onClick={handleClose}>
263
+ {t('componentInstallerClose')}
264
+ </Button>
265
+ ) : null}
266
+ </Box>
267
+ </>
268
+ )}
269
+ </Box>
270
+ )}
271
+ </Paper>
272
+ </Fade>
273
+ </ClickAwayListener>
274
+ </>
275
+ );
276
+ }}
277
+ </SessionPermission>
278
+ );
279
+ }
280
+
281
+ ComponentInstaller.propTypes = {
282
+ warnIcon: PropTypes.node,
283
+ did: PropTypes.string.isRequired,
284
+ noPermissionMute: PropTypes.bool,
285
+ onInstalled: PropTypes.func,
286
+ onError: PropTypes.func,
287
+ children: PropTypes.any.isRequired,
288
+ closeByOutSize: PropTypes.bool,
289
+ onClose: PropTypes.func,
290
+ fallback: PropTypes.node,
291
+ roles: PropTypes.array,
292
+ };
293
+
294
+ ComponentInstaller.defaultProps = {
295
+ warnIcon: null,
296
+ noPermissionMute: false,
297
+ onInstalled: null,
298
+ onError: null,
299
+ closeByOutSize: false,
300
+ onClose: null,
301
+ fallback: null,
302
+ roles: ['owner', 'admin'],
303
+ };
304
+
305
+ export default ComponentInstaller;
@@ -0,0 +1,22 @@
1
+ const translations = {
2
+ zh: {
3
+ componentInstallerTitle: '缺少组件',
4
+ componentInstallerInstall: '安装',
5
+ componentInstallerClose: '关闭',
6
+ componentInstallerRefresh: '重载页面',
7
+ componentInstallerSuccessInstalled: '安装成功,请重载页面查看变化。',
8
+ componentInstallerSuggestions: '请联系系统管理员安装该组件。',
9
+ componentInstallerNoDefinedInBlockletYML: '组件未在 blocklet.yml 中定义',
10
+ },
11
+ en: {
12
+ componentInstallerTitle: 'Missing component',
13
+ componentInstallerInstall: 'Install',
14
+ componentInstallerClose: 'Close',
15
+ componentInstallerRefresh: 'Refresh',
16
+ componentInstallerSuccessInstalled: 'Successfully installed, please refresh the page to see the changes.',
17
+ componentInstallerSuggestions: 'Please contact the system administrator to install the component.',
18
+ componentInstallerNoDefinedInBlockletYML: 'The component is not defined in blocklet.yml',
19
+ },
20
+ };
21
+
22
+ export default translations;
@@ -0,0 +1,68 @@
1
+ import { AUTH_SERVICE_PREFIX } from '@arcblock/did-connect/lib/constant';
2
+ import { useMemo, useRef, useState, useEffect } from 'react';
3
+
4
+ function useComponentInstalled({ did, onInstalled, onError }) {
5
+ const { optionalComponents, componentMountPoints } = window.blocklet;
6
+ const onInstalledRef = useRef({ onInstalled, onError });
7
+ onInstalledRef.current = { onInstalled, onError };
8
+
9
+ const optionalComponent = useMemo(() => {
10
+ if (!optionalComponents || !optionalComponents.length) {
11
+ return null;
12
+ }
13
+ const component = optionalComponents.find((c) => c.meta.did === did);
14
+ (component ? onInstalledRef.current.onError : onInstalledRef.current.onInstalled)?.(component);
15
+ return component;
16
+ }, [did, optionalComponents]);
17
+
18
+ const definedInBlockletYML = useMemo(() => {
19
+ if (optionalComponent) {
20
+ return true;
21
+ }
22
+ return (componentMountPoints || []).find((item) => item.did === did);
23
+ }, [optionalComponent, componentMountPoints, did]);
24
+
25
+ const installUrl = `${window.blocklet.appUrl}/${AUTH_SERVICE_PREFIX}/admin/components?install-component=${did}`;
26
+ const storeUrl = optionalComponent ? `${optionalComponent.meta.homepage}/blocklets/${did}` : '';
27
+ const [installStatus, setInstallStatus] = useState('');
28
+
29
+ useEffect(() => {
30
+ const handle = (event) => {
31
+ if (event.origin !== window.blocklet.appUrl) {
32
+ return;
33
+ }
34
+
35
+ if (event.data?.kind === 'component-installer' && event.data?.blocklet?.children) {
36
+ let hasChild = false;
37
+ event.data?.blocklet?.children.forEach((item) => {
38
+ if (item.meta?.did === did) {
39
+ hasChild = true;
40
+ setInstallStatus(item.status || 'waiting');
41
+ }
42
+ });
43
+ if (!hasChild) {
44
+ setInstallStatus('');
45
+ }
46
+ }
47
+ };
48
+ window.addEventListener('message', handle);
49
+ return () => {
50
+ window.removeEventListener('message', handle);
51
+ };
52
+ }, [did]);
53
+
54
+ const installStatusDone = installStatus === 'stopped' || installStatus === 'running';
55
+
56
+ return {
57
+ optionalComponent,
58
+ installed: !optionalComponent && definedInBlockletYML,
59
+ installUrl,
60
+ storeUrl,
61
+ installStatus,
62
+ setInstallStatus,
63
+ installStatusDone,
64
+ definedInBlockletYML,
65
+ };
66
+ }
67
+
68
+ export default useComponentInstalled;
@@ -80,7 +80,7 @@ const StyledInternalFooter = styled(InternalFooter)`
80
80
  border-top: 1px solid #eee;
81
81
  color: ${(props) => props.theme.palette.grey[600]};
82
82
  ${({ $bgcolor }) => $bgcolor && `background-color: ${$bgcolor};`}
83
- font-family: Lato, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
83
+ font-family: Inter, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
84
84
  'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
85
85
  `;
86
86
 
@@ -138,7 +138,7 @@ Header.defaultProps = {
138
138
 
139
139
  const StyledUxHeader = styled(ResponsiveHeader)`
140
140
  ${({ $bgcolor }) => `background-color: ${$bgcolor || '#fff'};`}
141
- font-family: Lato, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
141
+ font-family: Inter, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
142
142
  'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
143
143
  .header-logo {
144
144
  min-width: 44px;
@@ -54,7 +54,7 @@ export default function Passport({ user, ...rest }: { user: User } & StackProps)
54
54
  width={200}
55
55
  color={window.blocklet.passportColor}
56
56
  createPassportSvg={createPassportSvg}
57
- title={currentRole.role === x.role ? t('currentPassport') : ''}
57
+ title={currentRole && currentRole.role === x.role ? t('currentPassport') : ''}
58
58
  sx={{
59
59
  flexDirection: 'column',
60
60
  alignItems: 'center',
@@ -68,7 +68,7 @@ export default function Passport({ user, ...rest }: { user: User } & StackProps)
68
68
  justifyContent: 'center',
69
69
  backgroundColor: 'white',
70
70
  boxShadow:
71
- currentRole.role === x.role
71
+ currentRole && currentRole.role === x.role
72
72
  ? `0px 2px 4px 0px ${activeColor}, 0px 1px 2px -1px ${activeColor}, 0px 0px 0px 1px ${activeColor} !important`
73
73
  : '0px 2px 4px 0px rgba(2, 7, 19, 0.04), 0px 1px 2px -1px rgba(2, 7, 19, 0.08), 0px 0px 0px 1px rgba(2, 7, 19, 0.08) !important',
74
74
  },
package/src/index.ts CHANGED
@@ -6,4 +6,8 @@ export { default as Footer } from './Footer';
6
6
  export { default as Dashboard } from './Dashboard';
7
7
  // @ts-ignore
8
8
  export { default as Icon } from './Icon';
9
+ // @ts-ignore
10
+ export { default as ComponentInstaller } from './ComponentInstaller';
11
+ // @ts-ignore
12
+ export { default as useComponentInstaller } from './ComponentInstaller/use-component-installer';
9
13
  export * from './UserCenter';