@blocklet/ui-react 3.4.14 → 3.5.0

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.
Files changed (256) hide show
  1. package/lib/common/org-switch/use-org.d.ts +4 -4
  2. package/lib/common/ws.d.ts +22 -1
  3. package/package.json +10 -7
  4. package/.aigne/doc-smith/.local/afs-storage.sqlite3 +0 -0
  5. package/.aigne/doc-smith/config.yaml +0 -78
  6. package/.aigne/doc-smith/history.yaml +0 -14
  7. package/.aigne/doc-smith/media-description.yaml +0 -11
  8. package/.aigne/doc-smith/output/structure-plan.json +0 -255
  9. package/.aigne/doc-smith/translation-cache.yaml +0 -11
  10. package/.aigne/doc-smith/upload-cache.yaml +0 -528
  11. package/build.config.ts +0 -24
  12. package/docs/_sidebar.md +0 -19
  13. package/docs/assets/diagram/component-installer-diagram-0.ja.jpg +0 -0
  14. package/docs/assets/diagram/component-installer-diagram-0.jpg +0 -0
  15. package/docs/assets/diagram/component-installer-diagram-0.zh-TW.jpg +0 -0
  16. package/docs/assets/diagram/component-installer-diagram-0.zh.jpg +0 -0
  17. package/docs/assets/diagram/component-management-diagram-0.ja.jpg +0 -0
  18. package/docs/assets/diagram/component-management-diagram-0.jpg +0 -0
  19. package/docs/assets/diagram/component-management-diagram-0.zh-TW.jpg +0 -0
  20. package/docs/assets/diagram/component-management-diagram-0.zh.jpg +0 -0
  21. package/docs/assets/diagram/core-concepts-diagram-0.ja.jpg +0 -0
  22. package/docs/assets/diagram/core-concepts-diagram-0.jpg +0 -0
  23. package/docs/assets/diagram/core-concepts-diagram-0.zh-TW.jpg +0 -0
  24. package/docs/assets/diagram/core-concepts-diagram-0.zh.jpg +0 -0
  25. package/docs/assets/diagram/dashboard-diagram-0.ja.jpg +0 -0
  26. package/docs/assets/diagram/dashboard-diagram-0.jpg +0 -0
  27. package/docs/assets/diagram/dashboard-diagram-0.zh-TW.jpg +0 -0
  28. package/docs/assets/diagram/dashboard-diagram-0.zh.jpg +0 -0
  29. package/docs/assets/diagram/header-diagram-0.ja.jpg +0 -0
  30. package/docs/assets/diagram/header-diagram-0.jpg +0 -0
  31. package/docs/assets/diagram/header-diagram-0.zh-TW.jpg +0 -0
  32. package/docs/assets/diagram/header-diagram-0.zh.jpg +0 -0
  33. package/docs/assets/diagram/layout-diagram-0.ja.jpg +0 -0
  34. package/docs/assets/diagram/layout-diagram-0.jpg +0 -0
  35. package/docs/assets/diagram/layout-diagram-0.zh-TW.jpg +0 -0
  36. package/docs/assets/diagram/layout-diagram-0.zh.jpg +0 -0
  37. package/docs/assets/diagram/notifications-diagram-0.ja.jpg +0 -0
  38. package/docs/assets/diagram/notifications-diagram-0.jpg +0 -0
  39. package/docs/assets/diagram/notifications-diagram-0.zh-TW.jpg +0 -0
  40. package/docs/assets/diagram/notifications-diagram-0.zh.jpg +0 -0
  41. package/docs/assets/diagram/overview-diagram-0.ja.jpg +0 -0
  42. package/docs/assets/diagram/overview-diagram-0.jpg +0 -0
  43. package/docs/assets/diagram/overview-diagram-0.zh-TW.jpg +0 -0
  44. package/docs/assets/diagram/overview-diagram-0.zh.jpg +0 -0
  45. package/docs/assets/diagram/user-center-diagram-0.ja.jpg +0 -0
  46. package/docs/assets/diagram/user-center-diagram-0.jpg +0 -0
  47. package/docs/assets/diagram/user-center-diagram-0.zh-TW.jpg +0 -0
  48. package/docs/assets/diagram/user-center-diagram-0.zh.jpg +0 -0
  49. package/docs/assets/diagram/user-management-diagram-0.ja.jpg +0 -0
  50. package/docs/assets/diagram/user-management-diagram-0.jpg +0 -0
  51. package/docs/assets/diagram/user-management-diagram-0.zh-TW.jpg +0 -0
  52. package/docs/assets/diagram/user-management-diagram-0.zh.jpg +0 -0
  53. package/docs/assets/diagram/user-sessions-diagram-0.ja.jpg +0 -0
  54. package/docs/assets/diagram/user-sessions-diagram-0.jpg +0 -0
  55. package/docs/assets/diagram/user-sessions-diagram-0.zh-TW.jpg +0 -0
  56. package/docs/assets/diagram/user-sessions-diagram-0.zh.jpg +0 -0
  57. package/docs/components-component-management-blocklet-studio.ja.md +0 -194
  58. package/docs/components-component-management-blocklet-studio.md +0 -194
  59. package/docs/components-component-management-blocklet-studio.zh-TW.md +0 -194
  60. package/docs/components-component-management-blocklet-studio.zh.md +0 -194
  61. package/docs/components-component-management-component-installer.ja.md +0 -182
  62. package/docs/components-component-management-component-installer.md +0 -182
  63. package/docs/components-component-management-component-installer.zh-TW.md +0 -182
  64. package/docs/components-component-management-component-installer.zh.md +0 -182
  65. package/docs/components-component-management.ja.md +0 -30
  66. package/docs/components-component-management.md +0 -30
  67. package/docs/components-component-management.zh-TW.md +0 -30
  68. package/docs/components-component-management.zh.md +0 -30
  69. package/docs/components-layout-dashboard.ja.md +0 -185
  70. package/docs/components-layout-dashboard.md +0 -187
  71. package/docs/components-layout-dashboard.zh-TW.md +0 -185
  72. package/docs/components-layout-dashboard.zh.md +0 -185
  73. package/docs/components-layout-footer.ja.md +0 -165
  74. package/docs/components-layout-footer.md +0 -165
  75. package/docs/components-layout-footer.zh-TW.md +0 -165
  76. package/docs/components-layout-footer.zh.md +0 -165
  77. package/docs/components-layout-header.ja.md +0 -183
  78. package/docs/components-layout-header.md +0 -183
  79. package/docs/components-layout-header.zh-TW.md +0 -183
  80. package/docs/components-layout-header.zh.md +0 -183
  81. package/docs/components-layout.ja.md +0 -31
  82. package/docs/components-layout.md +0 -31
  83. package/docs/components-layout.zh-TW.md +0 -31
  84. package/docs/components-layout.zh.md +0 -31
  85. package/docs/components-notifications.ja.md +0 -125
  86. package/docs/components-notifications.md +0 -125
  87. package/docs/components-notifications.zh-TW.md +0 -125
  88. package/docs/components-notifications.zh.md +0 -125
  89. package/docs/components-user-management-user-center.ja.md +0 -148
  90. package/docs/components-user-management-user-center.md +0 -147
  91. package/docs/components-user-management-user-center.zh-TW.md +0 -148
  92. package/docs/components-user-management-user-center.zh.md +0 -148
  93. package/docs/components-user-management-user-sessions.ja.md +0 -121
  94. package/docs/components-user-management-user-sessions.md +0 -123
  95. package/docs/components-user-management-user-sessions.zh-TW.md +0 -121
  96. package/docs/components-user-management-user-sessions.zh.md +0 -121
  97. package/docs/components-user-management.ja.md +0 -49
  98. package/docs/components-user-management.md +0 -51
  99. package/docs/components-user-management.zh-TW.md +0 -49
  100. package/docs/components-user-management.zh.md +0 -49
  101. package/docs/components-utilities-icon.ja.md +0 -106
  102. package/docs/components-utilities-icon.md +0 -106
  103. package/docs/components-utilities-icon.zh-TW.md +0 -106
  104. package/docs/components-utilities-icon.zh.md +0 -106
  105. package/docs/components-utilities.ja.md +0 -136
  106. package/docs/components-utilities.md +0 -136
  107. package/docs/components-utilities.zh-TW.md +0 -136
  108. package/docs/components-utilities.zh.md +0 -136
  109. package/docs/components.ja.md +0 -27
  110. package/docs/components.md +0 -27
  111. package/docs/components.zh-TW.md +0 -27
  112. package/docs/components.zh.md +0 -27
  113. package/docs/core-concepts.ja.md +0 -134
  114. package/docs/core-concepts.md +0 -135
  115. package/docs/core-concepts.zh-TW.md +0 -134
  116. package/docs/core-concepts.zh.md +0 -134
  117. package/docs/getting-started.ja.md +0 -132
  118. package/docs/getting-started.md +0 -132
  119. package/docs/getting-started.zh-TW.md +0 -132
  120. package/docs/getting-started.zh.md +0 -132
  121. package/docs/hooks-api.ja.md +0 -214
  122. package/docs/hooks-api.md +0 -214
  123. package/docs/hooks-api.zh-TW.md +0 -214
  124. package/docs/hooks-api.zh.md +0 -214
  125. package/docs/how-to-guides.ja.md +0 -413
  126. package/docs/how-to-guides.md +0 -413
  127. package/docs/how-to-guides.zh-TW.md +0 -413
  128. package/docs/how-to-guides.zh.md +0 -413
  129. package/docs/overview.ja.md +0 -51
  130. package/docs/overview.md +0 -51
  131. package/docs/overview.zh-TW.md +0 -51
  132. package/docs/overview.zh.md +0 -51
  133. package/glossary.md +0 -12
  134. package/src/@types/index.ts +0 -230
  135. package/src/@types/shims.d.ts +0 -18
  136. package/src/BlockletStudio/README.md +0 -116
  137. package/src/BlockletStudio/index.tsx +0 -145
  138. package/src/ComponentInstaller/ComponentInstaller.stories.jsx +0 -16
  139. package/src/ComponentInstaller/index.jsx +0 -207
  140. package/src/ComponentInstaller/installer-item.jsx +0 -129
  141. package/src/ComponentInstaller/locales.js +0 -22
  142. package/src/ComponentInstaller/use-component-installed.js +0 -88
  143. package/src/ComponentManager/components/add-component.tsx +0 -136
  144. package/src/ComponentManager/components/check-component.tsx +0 -3
  145. package/src/ComponentManager/components/publish-component.tsx +0 -90
  146. package/src/ComponentManager/components/resource-dialog.tsx +0 -91
  147. package/src/ComponentManager/index.tsx +0 -3
  148. package/src/ComponentManager/libs/locales.ts +0 -15
  149. package/src/Dashboard/Dashboard.stories.jsx +0 -20
  150. package/src/Dashboard/app-shell/app-badge.stories.tsx +0 -64
  151. package/src/Dashboard/app-shell/app-badge.tsx +0 -94
  152. package/src/Dashboard/app-shell/app-header.tsx +0 -104
  153. package/src/Dashboard/app-shell/app-info-context.tsx +0 -182
  154. package/src/Dashboard/app-shell/badges/app-badge-default.tsx +0 -130
  155. package/src/Dashboard/app-shell/badges/app-badge-did.tsx +0 -28
  156. package/src/Dashboard/app-shell/badges/app-badge-state.tsx +0 -40
  157. package/src/Dashboard/app-shell/badges/app-badge-switch.tsx +0 -72
  158. package/src/Dashboard/app-shell/badges/app-badge-version.tsx +0 -60
  159. package/src/Dashboard/app-shell/index.ts +0 -5
  160. package/src/Dashboard/index.jsx +0 -184
  161. package/src/Footer/Footer.stories.jsx +0 -33
  162. package/src/Footer/brand.jsx +0 -81
  163. package/src/Footer/copyright.jsx +0 -22
  164. package/src/Footer/index.jsx +0 -111
  165. package/src/Footer/internal-footer.jsx +0 -139
  166. package/src/Footer/layout/plain.jsx +0 -55
  167. package/src/Footer/layout/row.jsx +0 -43
  168. package/src/Footer/layout/standard.jsx +0 -114
  169. package/src/Footer/links.jsx +0 -321
  170. package/src/Footer/social-media.jsx +0 -55
  171. package/src/Header/Header.stories.jsx +0 -30
  172. package/src/Header/index.tsx +0 -259
  173. package/src/Icon/Icon.stories.jsx +0 -12
  174. package/src/Icon/index.tsx +0 -87
  175. package/src/Notifications/Snackbar.tsx +0 -261
  176. package/src/Notifications/hooks/use-title.tsx +0 -254
  177. package/src/Notifications/hooks/use-width.tsx +0 -16
  178. package/src/Notifications/utils.ts +0 -246
  179. package/src/UserCenter/assets/banner.png +0 -0
  180. package/src/UserCenter/components/config-inviter.tsx +0 -48
  181. package/src/UserCenter/components/config-profile.tsx +0 -99
  182. package/src/UserCenter/components/danger-zone.tsx +0 -82
  183. package/src/UserCenter/components/editable-field.tsx +0 -273
  184. package/src/UserCenter/components/fallback.tsx +0 -57
  185. package/src/UserCenter/components/nft-preview.tsx +0 -84
  186. package/src/UserCenter/components/nft.tsx +0 -279
  187. package/src/UserCenter/components/notification.tsx +0 -319
  188. package/src/UserCenter/components/passport.tsx +0 -107
  189. package/src/UserCenter/components/privacy.tsx +0 -120
  190. package/src/UserCenter/components/settings.tsx +0 -170
  191. package/src/UserCenter/components/status-dialog/date-picker.tsx +0 -77
  192. package/src/UserCenter/components/status-dialog/index.tsx +0 -293
  193. package/src/UserCenter/components/status-selector/duration-menu.tsx +0 -90
  194. package/src/UserCenter/components/status-selector/index.tsx +0 -58
  195. package/src/UserCenter/components/status-selector/menu-item.tsx +0 -56
  196. package/src/UserCenter/components/storage/action.tsx +0 -49
  197. package/src/UserCenter/components/storage/connected.tsx +0 -61
  198. package/src/UserCenter/components/storage/delete.tsx +0 -72
  199. package/src/UserCenter/components/storage/disconnect.tsx +0 -40
  200. package/src/UserCenter/components/storage/icons/empty-spaces-nft.svg +0 -1
  201. package/src/UserCenter/components/storage/icons/long-arrow.svg +0 -5
  202. package/src/UserCenter/components/storage/icons/space-connected.svg +0 -3
  203. package/src/UserCenter/components/storage/icons/space-disconnect.svg +0 -3
  204. package/src/UserCenter/components/storage/index.tsx +0 -41
  205. package/src/UserCenter/components/storage/preview-nft.tsx +0 -72
  206. package/src/UserCenter/components/third-party-login/index.tsx +0 -199
  207. package/src/UserCenter/components/third-party-login/third-party-item.tsx +0 -296
  208. package/src/UserCenter/components/user-center.tsx +0 -787
  209. package/src/UserCenter/components/user-info/address.tsx +0 -143
  210. package/src/UserCenter/components/user-info/index.tsx +0 -4
  211. package/src/UserCenter/components/user-info/link-preview-input.tsx +0 -274
  212. package/src/UserCenter/components/user-info/metadata.tsx +0 -658
  213. package/src/UserCenter/components/user-info/social-actions/chat.tsx +0 -43
  214. package/src/UserCenter/components/user-info/social-actions/follow.tsx +0 -23
  215. package/src/UserCenter/components/user-info/social-actions/index.tsx +0 -17
  216. package/src/UserCenter/components/user-info/switch-role.tsx +0 -42
  217. package/src/UserCenter/components/user-info/timezone-select.tsx +0 -119
  218. package/src/UserCenter/components/user-info/user-basic-info.tsx +0 -292
  219. package/src/UserCenter/components/user-info/user-info-item.tsx +0 -54
  220. package/src/UserCenter/components/user-info/user-info.tsx +0 -91
  221. package/src/UserCenter/components/user-info/user-status.tsx +0 -234
  222. package/src/UserCenter/components/user-info/utils.ts +0 -320
  223. package/src/UserCenter/components/webhook-item.tsx +0 -248
  224. package/src/UserCenter/index.tsx +0 -1
  225. package/src/UserCenter/libs/locales.ts +0 -378
  226. package/src/UserCenter/libs/utils.ts +0 -30
  227. package/src/UserSessions/components/user-session-info.tsx +0 -78
  228. package/src/UserSessions/components/user-sessions.tsx +0 -545
  229. package/src/UserSessions/index.tsx +0 -1
  230. package/src/UserSessions/libs/locales.ts +0 -60
  231. package/src/UserSessions/libs/utils.ts +0 -82
  232. package/src/blocklets.js +0 -195
  233. package/src/common/domain-warning.jsx +0 -178
  234. package/src/common/header-addons.jsx +0 -119
  235. package/src/common/link-blocker.jsx +0 -20
  236. package/src/common/notification-addon.jsx +0 -135
  237. package/src/common/org-switch/avatar-uploader.jsx +0 -271
  238. package/src/common/org-switch/create.jsx +0 -267
  239. package/src/common/org-switch/index.jsx +0 -407
  240. package/src/common/org-switch/locales.js +0 -52
  241. package/src/common/org-switch/use-org.jsx +0 -79
  242. package/src/common/overridable-theme-provider.jsx +0 -17
  243. package/src/common/wallet-hidden-topbar.js +0 -14
  244. package/src/common/wizard-modal.jsx +0 -200
  245. package/src/common/ws.js +0 -68
  246. package/src/contexts/config-user-space.tsx +0 -88
  247. package/src/contexts/user-followers.tsx +0 -54
  248. package/src/hooks/use-follow.tsx +0 -75
  249. package/src/hooks/use-mobile.tsx +0 -6
  250. package/src/index.ts +0 -16
  251. package/src/libs/constant.ts +0 -1
  252. package/src/libs/spaces.tsx +0 -18
  253. package/src/libs/with-hide-when-embed.tsx +0 -24
  254. package/src/types.js +0 -45
  255. package/src/utils.js +0 -161
  256. package/vite.config.mjs +0 -34
@@ -1,135 +0,0 @@
1
- import PropTypes from 'prop-types';
2
- import { useCallback, useEffect, useMemo } from 'react';
3
- import { IconButton, Badge } from '@mui/material';
4
- import { useSnackbar } from 'notistack';
5
- import NotificationsOutlinedIcon from '@arcblock/icons/lib/Notification';
6
- import { useCreation } from 'ahooks';
7
- import { EVENTS, WELLKNOWN_SERVICE_PATH_PREFIX, ROLES } from '@abtnode/constant';
8
- import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
9
- import { compareVersions } from '@arcblock/ux/lib/Util';
10
- import { joinURL, withQuery } from 'ufo';
11
- import { useListenWsClient } from './ws';
12
- import NotificationSnackbar from '../Notifications/Snackbar';
13
-
14
- const viewAllUrl = joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, 'user', 'notifications');
15
-
16
- const getNotificationLink = (notification) => {
17
- return withQuery(viewAllUrl, {
18
- id: notification.id,
19
- severity: notification.severity || 'all',
20
- componentDid: notification.source === 'system' ? 'system' : notification.componentDid || 'all',
21
- });
22
- };
23
-
24
- export default function NotificationAddon({ session = {} }) {
25
- const { unReadCount, user, setUnReadCount } = session;
26
- const userDid = useCreation(() => user?.did, [user]);
27
-
28
- const isAdmin = useCreation(() => user?.role && [ROLES.ADMIN, ROLES.OWNER].includes(user?.role), [user?.role]);
29
-
30
- const { enqueueSnackbar } = useSnackbar();
31
- const browser = useBrowser();
32
-
33
- const isPreviewUser = useMemo(() => {
34
- return !userDid || user?.isPreviewUser || userDid === window.blocklet.did;
35
- }, [userDid, user]);
36
-
37
- // 在 ArcSphere 和 Wallet 端隐藏, 消息的 toast 提示,因为有 native 的通知
38
- const hiddenNotificationToast = useMemo(() => {
39
- return browser.arcSphere || browser.wallet;
40
- }, [browser]);
41
-
42
- const serverVersion = useCreation(() => {
43
- return window.blocklet?.serverVersion;
44
- }, []);
45
-
46
- const wsClient = useListenWsClient('user');
47
-
48
- const listenEvent = useCreation(
49
- () => `${window.blocklet.did}/${userDid}/${EVENTS.NOTIFICATION_BLOCKLET_CREATE}`,
50
- [userDid]
51
- );
52
- const readListenEvent = useCreation(
53
- () => `${window.blocklet.did}/${userDid}/${EVENTS.NOTIFICATION_BLOCKLET_READ}`,
54
- [userDid]
55
- );
56
-
57
- const listenCallback = useCallback(
58
- (notification) => {
59
- const { receivers, source } = notification ?? {};
60
-
61
- const { receiver: notificationReceiver } = receivers[0] ?? {};
62
- const shouldUpdate = isAdmin || source !== 'system';
63
- if (notificationReceiver === userDid && shouldUpdate) {
64
- setUnReadCount((x) => x + 1);
65
- // 显示通知, 如果是系统通知则不需要显示, 如果是移动端不需要显示
66
- // 兼容代码,如果 server 没有升级那么不需要提示
67
- const isCompatible = compareVersions(serverVersion, '1.16.42-beta-20250407');
68
- if (!hiddenNotificationToast && notification.source === 'component' && isCompatible) {
69
- const link = getNotificationLink(notification);
70
- const { severity, description } = notification || {};
71
- const disableAutoHide = ['error', 'warning'].includes(severity) || notification.sticky;
72
- enqueueSnackbar(description, {
73
- variant: severity,
74
- autoHideDuration: disableAutoHide ? null : 5000,
75
- anchorOrigin: {
76
- vertical: 'top',
77
- horizontal: 'center',
78
- },
79
- // eslint-disable-next-line react/no-unstable-nested-components
80
- content: (key) => <NotificationSnackbar viewAllUrl={link} keyId={key} notification={notification} />,
81
- });
82
- }
83
- }
84
- },
85
- [userDid, setUnReadCount, enqueueSnackbar, serverVersion, hiddenNotificationToast, isAdmin]
86
- );
87
-
88
- const readListenCallback = useCallback(
89
- (data) => {
90
- const { receiver, readCount } = data ?? {};
91
-
92
- if (receiver === userDid) {
93
- setUnReadCount((x) => Math.max(x - readCount, 0));
94
- }
95
- },
96
- [userDid, setUnReadCount]
97
- );
98
-
99
- useEffect(() => {
100
- if (wsClient && !isPreviewUser) {
101
- wsClient.on(listenEvent, listenCallback);
102
- wsClient.on(readListenEvent, readListenCallback);
103
- }
104
- return () => {
105
- if (wsClient && !isPreviewUser) {
106
- wsClient.off(listenEvent, listenCallback);
107
- wsClient.off(readListenEvent, readListenCallback);
108
- }
109
- };
110
- }, [wsClient, setUnReadCount, listenCallback, listenEvent, readListenCallback, readListenEvent, isPreviewUser]);
111
-
112
- if (!session.user || !viewAllUrl) {
113
- return null;
114
- }
115
-
116
- return (
117
- <IconButton
118
- size="medium"
119
- variant="outlined"
120
- href={withQuery(viewAllUrl, { read: unReadCount <= 0 })}
121
- sx={{
122
- '&:hover': {
123
- borderRadius: '50%',
124
- },
125
- }}>
126
- <Badge badgeContent={unReadCount} color="error" invisible={unReadCount === 0}>
127
- <NotificationsOutlinedIcon style={{ width: 'auto', height: 24 }} />
128
- </Badge>
129
- </IconButton>
130
- );
131
- }
132
-
133
- NotificationAddon.propTypes = {
134
- session: PropTypes.object,
135
- };
@@ -1,271 +0,0 @@
1
- /**
2
- * AvatarUploader - 组织头像上传组件
3
- *
4
- * 用于创建和编辑组织时上传头像
5
- * 支持图片裁剪、旋转等编辑功能
6
- * 上传成功后返回文件路径供表单使用
7
- */
8
- import { lazy, Suspense, useRef, useMemo, useState } from 'react';
9
- import noop from 'lodash/noop';
10
- import PropTypes from 'prop-types';
11
- import { Box, CircularProgress } from '@mui/material';
12
- import { styled } from '@arcblock/ux/lib/Theme';
13
- import { joinURL } from 'ufo';
14
- import { useMemoizedFn } from 'ahooks';
15
- import CameraAltIcon from '@mui/icons-material/CameraAlt';
16
- import { translate } from '@arcblock/ux/lib/Locale/util';
17
- import Img from '@arcblock/ux/lib/Img';
18
-
19
- import translations from './locales';
20
-
21
- // eslint-disable-next-line import/no-unresolved
22
- const Uploader = lazy(() => import('@blocklet/uploader').then((res) => ({ default: res.Uploader })));
23
-
24
- // 配置常量
25
- const UPLOAD_PREFIX = '/blocklet';
26
- const ALLOWED_FILE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.bmp', '.ico'];
27
- const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
28
- const MIN_CROP_SIZE = 256;
29
- const ICON_SIZE_RATIO = 3;
30
-
31
- /**
32
- * 构建组织头像显示 URL
33
- * @param {string} prefix - URL 前缀
34
- * @param {string} teamDid - 团队 DID
35
- * @param {string} orgId - 组织 ID
36
- * @param {string} avatar - 头像文件名
37
- * @returns {string} 完整的 URL 路径
38
- */
39
- function buildAvatarDisplayUrl(prefix, teamDid, avatar) {
40
- return joinURL(prefix, UPLOAD_PREFIX, teamDid, 'orgs', 'avatar', avatar);
41
- }
42
-
43
- /**
44
- * 构建头像上传 API URL(不需要 orgId)
45
- * @param {string} prefix - URL 前缀
46
- * @param {string} teamDid - 团队 DID
47
- * @returns {string} 上传 API URL
48
- */
49
- function buildAvatarUploadUrl(prefix, teamDid) {
50
- return joinURL(prefix, UPLOAD_PREFIX, teamDid, 'orgs', 'avatar', 'upload');
51
- }
52
-
53
- export default function AvatarUploader({
54
- org = null,
55
- size = 80,
56
- teamDid: teamDidProp = '',
57
- prefix = '/.well-known/service',
58
- locale = 'en',
59
- headers = noop,
60
- onChange = noop,
61
- onError = noop,
62
- editable = true,
63
- }) {
64
- const t = useMemoizedFn((key, data = {}) => {
65
- return translate(translations, key, locale, 'en', data);
66
- });
67
- const uploaderRef = useRef(null);
68
- const [uploading, setUploading] = useState(false);
69
- // 本地预览的头像路径(用于新上传的图片)
70
- const [localAvatar, setLocalAvatar] = useState('');
71
-
72
- // 获取 teamDid:优先使用 prop,否则从 window.blocklet.did 获取
73
- const teamDid = useMemo(() => {
74
- if (teamDidProp) return teamDidProp;
75
- // eslint-disable-next-line no-undef
76
- return typeof window !== 'undefined' ? window.blocklet?.did : '';
77
- }, [teamDidProp]);
78
-
79
- // 构建上传 API URL(不需要 orgId)
80
- const uploadApiUrl = useMemo(() => {
81
- if (!teamDid) return null;
82
- return buildAvatarUploadUrl(prefix, teamDid);
83
- }, [prefix, teamDid]);
84
-
85
- // 是否可以上传(需要有 teamDid 且 editable 为 true)
86
- const canUpload = editable && !!uploadApiUrl;
87
-
88
- // 打开上传弹窗
89
- const handleOpen = useMemoizedFn(() => {
90
- if (canUpload && !uploading) {
91
- uploaderRef.current?.open();
92
- }
93
- });
94
-
95
- // 上传完成处理 - 返回文件路径
96
- const handleFinish = useMemoizedFn((result) => {
97
- uploaderRef.current?.close();
98
- setUploading(false);
99
- const { avatarPath } = result.data;
100
- setLocalAvatar(result.uploadURL);
101
- onChange(avatarPath);
102
- });
103
-
104
- // 上传开始处理
105
- const handleUploadStart = useMemoizedFn(() => {
106
- setUploading(true);
107
- });
108
-
109
- // 错误处理
110
- const handleError = useMemoizedFn((error) => {
111
- setUploading(false);
112
- console.error('Avatar upload failed:', error);
113
- onError(error);
114
- });
115
-
116
- // 构建头像显示 URL
117
- const avatarUrl = useMemo(() => {
118
- // 优先使用本地上传的头像
119
- if (localAvatar) {
120
- return localAvatar;
121
- }
122
- const avatar = org?.avatar;
123
- if (!avatar) return null;
124
- if (org?.id) {
125
- return buildAvatarDisplayUrl(prefix, teamDid, avatar);
126
- }
127
- if (avatar.startsWith('http://') || avatar.startsWith('https://') || avatar.startsWith('/')) {
128
- return avatar;
129
- }
130
- return joinURL(prefix, avatar);
131
- }, [localAvatar, org?.avatar, org?.id, prefix, teamDid]);
132
-
133
- // 获取显示名称
134
- const displayName = org?.name || '';
135
-
136
- return (
137
- <Root $size={size} $editable={editable} $uploading={uploading} onClick={handleOpen}>
138
- {uploadApiUrl && (
139
- <Suspense fallback={null}>
140
- <Uploader
141
- ref={uploaderRef}
142
- locale={locale}
143
- popup
144
- onUploadFinish={handleFinish}
145
- onUploadStart={handleUploadStart}
146
- onError={handleError}
147
- plugins={['ImageEditor']}
148
- installerProps={{ disabled: true }}
149
- apiPathProps={{
150
- uploader: uploadApiUrl,
151
- disableMediaKitPrefix: true,
152
- disableMediaKitStatus: true,
153
- }}
154
- coreProps={{
155
- restrictions: {
156
- allowedFileExts: ALLOWED_FILE_EXTENSIONS,
157
- maxFileSize: MAX_FILE_SIZE,
158
- maxNumberOfFiles: 1,
159
- },
160
- }}
161
- dashboardProps={{
162
- autoOpen: 'imageEditor',
163
- }}
164
- imageEditorProps={{
165
- actions: {
166
- revert: true,
167
- rotate: true,
168
- granularRotate: true,
169
- flip: true,
170
- zoomIn: true,
171
- zoomOut: true,
172
- cropSquare: false,
173
- cropWidescreen: false,
174
- cropWidescreenVertical: false,
175
- },
176
- cropperOptions: {
177
- autoCrop: true,
178
- autoCropArea: 1,
179
- aspectRatio: 1,
180
- initialAspectRatio: 1,
181
- croppedCanvasOptions: {
182
- minWidth: MIN_CROP_SIZE,
183
- minHeight: MIN_CROP_SIZE,
184
- },
185
- },
186
- }}
187
- tusProps={{
188
- headers,
189
- }}
190
- />
191
- </Suspense>
192
- )}
193
- {/* 使用 Img 组件显示头像,自动处理加载错误和占位图片 */}
194
- <Img
195
- key={avatarUrl || 'no-avatar'}
196
- src={avatarUrl || ''}
197
- alt={displayName}
198
- width={size}
199
- height={size}
200
- ratio={1}
201
- size="cover"
202
- position="center"
203
- lazy={false}
204
- style={{
205
- borderRadius: '50%',
206
- overflow: 'hidden',
207
- }}
208
- />
209
- {editable && (
210
- <Box className="upload-overlay">
211
- {uploading ? (
212
- <CircularProgress size={size / ICON_SIZE_RATIO} sx={{ color: 'white' }} />
213
- ) : (
214
- <>
215
- <CameraAltIcon sx={{ fontSize: size / ICON_SIZE_RATIO, color: 'white' }} />
216
- <Box component="span" sx={{ fontSize: 12, color: 'white' }}>
217
- {t('upload')}
218
- </Box>
219
- </>
220
- )}
221
- </Box>
222
- )}
223
- </Root>
224
- );
225
- }
226
-
227
- AvatarUploader.propTypes = {
228
- org: PropTypes.shape({
229
- id: PropTypes.string,
230
- name: PropTypes.string,
231
- avatar: PropTypes.string,
232
- }),
233
- size: PropTypes.number,
234
- teamDid: PropTypes.string,
235
- prefix: PropTypes.string,
236
- headers: PropTypes.func,
237
- onChange: PropTypes.func,
238
- onError: PropTypes.func,
239
- editable: PropTypes.bool,
240
- locale: PropTypes.string,
241
- };
242
-
243
- const Root = styled(Box, {
244
- shouldForwardProp: (prop) => !['$size', '$editable', '$uploading'].includes(prop),
245
- })(({ $size, $editable, $uploading }) => ({
246
- position: 'relative',
247
- width: $size,
248
- height: $size,
249
- borderRadius: '50%',
250
- overflow: 'hidden',
251
- cursor: $editable && !$uploading ? 'pointer' : 'default',
252
-
253
- '.upload-overlay': {
254
- position: 'absolute',
255
- top: 0,
256
- left: 0,
257
- right: 0,
258
- bottom: 0,
259
- display: 'flex',
260
- flexDirection: 'column',
261
- alignItems: 'center',
262
- justifyContent: 'center',
263
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
264
- opacity: $uploading ? 1 : 0,
265
- transition: 'opacity 0.2s ease-in-out',
266
- },
267
-
268
- '&:hover .upload-overlay': {
269
- opacity: $editable ? 1 : 0,
270
- },
271
- }));
@@ -1,267 +0,0 @@
1
- /* eslint-disable @typescript-eslint/naming-convention */
2
- /**
3
- * OrgMutateDialog - 组织创建/编辑弹窗
4
- *
5
- * 支持两种模式:
6
- * - 创建模式:新建组织,头像必填
7
- * - 编辑模式:编辑已有组织信息
8
- */
9
- import { useState, useEffect } from 'react';
10
- import PropTypes from 'prop-types';
11
- import { useReactive, useMemoizedFn } from 'ahooks';
12
- import noop from 'lodash/noop';
13
- import Dialog from '@arcblock/ux/lib/Dialog';
14
- import { CircularProgress, DialogContentText, Typography, TextField, Alert, Box } from '@mui/material';
15
- import Toast from '@arcblock/ux/lib/Toast';
16
- import Button from '@arcblock/ux/lib/Button';
17
- import { translate } from '@arcblock/ux/lib/Locale/util';
18
-
19
- import { formatAxiosError } from '../../UserCenter/libs/utils';
20
- import useOrg from './use-org';
21
- import translations from './locales';
22
- import AvatarUploader from './avatar-uploader';
23
-
24
- // 操作模式
25
- const MODE_CREATE = 'create';
26
- const MODE_EDIT = 'edit';
27
-
28
- export default function OrgMutateDialog({
29
- mode = MODE_CREATE,
30
- org = null,
31
- onSuccess = noop,
32
- onCancel = noop,
33
- locale = 'en',
34
- teamDid: teamDidProp = '',
35
- prefix = '/.well-known/service',
36
- headers = noop,
37
- }) {
38
- const isEditMode = mode === MODE_EDIT;
39
- const [loading, setLoading] = useState(false);
40
- const [error, setError] = useState('');
41
- const { createOrg, updateOrg } = useOrg();
42
-
43
- // 获取 teamDid:优先使用 prop,否则从 window.blocklet.did 获取
44
- // eslint-disable-next-line no-undef
45
- const teamDid = teamDidProp || (typeof window !== 'undefined' ? window.blocklet?.did : '');
46
-
47
- const t = useMemoizedFn((key, data = {}) => {
48
- return translate(translations, key, locale, 'en', data);
49
- });
50
-
51
- const form = useReactive({
52
- name: '',
53
- description: '',
54
- avatar: '',
55
- });
56
-
57
- // 编辑模式下初始化表单数据
58
- useEffect(() => {
59
- if (isEditMode && org) {
60
- form.name = org.name || '';
61
- form.description = org.description || '';
62
- form.avatar = org.avatar || '';
63
- }
64
- // eslint-disable-next-line react-hooks/exhaustive-deps
65
- }, [isEditMode, org]);
66
-
67
- // 头像变化处理
68
- const handleAvatarChange = useMemoizedFn((avatarPath) => {
69
- setError('');
70
- form.avatar = avatarPath;
71
- });
72
-
73
- // 表单验证
74
- const validateForm = useMemoizedFn(() => {
75
- const _name = form.name.trim();
76
- if (!_name) {
77
- setError(t('nameEmpty'));
78
- return false;
79
- }
80
-
81
- if (_name.length > 25) {
82
- setError(t('nameTooLong', { length: 25 }));
83
- return false;
84
- }
85
-
86
- const _description = form.description.trim();
87
- if (_description.length > 255) {
88
- setError(t('descriptionTooLong', { length: 255 }));
89
- return false;
90
- }
91
-
92
- // 头像必填验证
93
- if (!form.avatar) {
94
- setError(t('avatarEmpty'));
95
- return false;
96
- }
97
-
98
- return true;
99
- });
100
-
101
- // 提交处理
102
- const onSubmit = async () => {
103
- if (!validateForm()) {
104
- return;
105
- }
106
-
107
- setError('');
108
- setLoading(true);
109
-
110
- const _name = form.name.trim();
111
- const _description = form.description.trim();
112
- const _avatar = form.avatar;
113
-
114
- try {
115
- if (isEditMode && org?.id) {
116
- // 编辑模式:更新组织
117
- await updateOrg(org.id, {
118
- name: _name,
119
- description: _description,
120
- avatar: _avatar,
121
- });
122
- } else {
123
- // 创建模式:新建组织
124
- await createOrg({
125
- name: _name,
126
- description: _description,
127
- avatar: _avatar,
128
- });
129
- }
130
- onSuccess();
131
- } catch (err) {
132
- console.error(err);
133
- const errMsg = formatAxiosError(err);
134
- setError(errMsg);
135
- Toast.error(errMsg);
136
- } finally {
137
- setLoading(false);
138
- }
139
- };
140
-
141
- // 对话框标题
142
- const dialogTitle = isEditMode ? t('mutate.title', { mode: t('edit') }) : t('mutate.title', { mode: t('create') });
143
-
144
- // 提交按钮文本
145
- const submitButtonText = isEditMode ? t('save') : t('create');
146
-
147
- return (
148
- <Dialog
149
- title={dialogTitle}
150
- fullWidth
151
- open
152
- onClose={onCancel}
153
- showCloseButton={false}
154
- actions={
155
- <>
156
- <Button onClick={onCancel} color="inherit">
157
- {t('cancel')}
158
- </Button>
159
- <Button
160
- data-cy="mutate-org-confirm"
161
- onClick={onSubmit}
162
- color="primary"
163
- disabled={loading}
164
- variant="contained"
165
- autoFocus>
166
- {loading && <CircularProgress size={16} sx={{ mr: 1 }} />}
167
- {submitButtonText}
168
- </Button>
169
- </>
170
- }>
171
- <DialogContentText component="div">
172
- {/* 头像上传区域 */}
173
- <Box
174
- sx={{
175
- display: 'flex',
176
- flexDirection: 'column',
177
- alignItems: 'center',
178
- py: 2,
179
- }}>
180
- <AvatarUploader
181
- org={isEditMode ? org : { name: form.name }}
182
- size={90}
183
- teamDid={teamDid}
184
- prefix={prefix}
185
- headers={headers}
186
- onChange={handleAvatarChange}
187
- value={form.avatar}
188
- editable
189
- />
190
- <Typography variant="caption" color="text.secondary" sx={{ mt: 1 }}>
191
- {t('avatar')}
192
- </Typography>
193
- </Box>
194
-
195
- {/* 表单字段 */}
196
- <Typography component="div" style={{ marginTop: 16 }}>
197
- <TextField
198
- label={t('mutate.name')}
199
- autoComplete="off"
200
- variant="outlined"
201
- name="name"
202
- data-cy="mutate-org-input-name"
203
- fullWidth
204
- autoFocus
205
- value={form.name}
206
- onChange={(e) => {
207
- setError('');
208
- form.name = e.target.value;
209
- }}
210
- disabled={loading}
211
- required
212
- />
213
- </Typography>
214
-
215
- <Typography component="div" style={{ marginTop: 16, marginBottom: 16 }}>
216
- <TextField
217
- label={t('mutate.description')}
218
- autoComplete="off"
219
- variant="outlined"
220
- name="description"
221
- data-cy="mutate-org-input-description"
222
- fullWidth
223
- value={form.description}
224
- onChange={(e) => {
225
- setError('');
226
- form.description = e.target.value;
227
- }}
228
- disabled={loading}
229
- multiline
230
- rows={3}
231
- />
232
- </Typography>
233
- </DialogContentText>
234
-
235
- {!!error && (
236
- <Alert severity="error" style={{ width: '100%', margin: 0 }}>
237
- {error}
238
- </Alert>
239
- )}
240
- </Dialog>
241
- );
242
- }
243
-
244
- OrgMutateDialog.propTypes = {
245
- /** 操作模式:'create' 创建 | 'edit' 编辑 */
246
- mode: PropTypes.oneOf([MODE_CREATE, MODE_EDIT]),
247
- /** 编辑模式下的组织数据 */
248
- org: PropTypes.shape({
249
- id: PropTypes.string,
250
- name: PropTypes.string,
251
- description: PropTypes.string,
252
- avatar: PropTypes.string,
253
- }),
254
- onSuccess: PropTypes.func,
255
- onCancel: PropTypes.func,
256
- locale: PropTypes.string,
257
- teamDid: PropTypes.string,
258
- prefix: PropTypes.string,
259
- headers: PropTypes.func,
260
- };
261
-
262
- // 导出模式常量供外部使用
263
- OrgMutateDialog.MODE_CREATE = MODE_CREATE;
264
- OrgMutateDialog.MODE_EDIT = MODE_EDIT;
265
-
266
- // 保持向后兼容的别名
267
- export { OrgMutateDialog as CreateOrgDialog };