@blocklet/ui-react 2.4.21 → 2.4.24

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.
@@ -7,12 +7,12 @@ exports.default = void 0;
7
7
 
8
8
  var _react = require("react");
9
9
 
10
+ var _Session = require("@arcblock/did-connect/lib/Session");
11
+
10
12
  var _context = require("@arcblock/ux/lib/Locale/context");
11
13
 
12
14
  var _dashboard = _interopRequireDefault(require("@arcblock/ux/lib/Layout/dashboard"));
13
15
 
14
- var _ClickToCopy = _interopRequireDefault(require("@arcblock/ux/lib/ClickToCopy"));
15
-
16
16
  var _Address = _interopRequireDefault(require("@arcblock/did-connect/lib/Address"));
17
17
 
18
18
  var _Avatar = _interopRequireDefault(require("@arcblock/did-connect/lib/Avatar"));
@@ -39,17 +39,43 @@ function _objectWithoutProperties(source, excluded) { if (source == null) return
39
39
 
40
40
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
41
41
 
42
+ /**
43
+ * 根据 role 筛选 nav, 规则:
44
+ * - 如果是枝结点, 必须至少存在一个符合条件的子结点
45
+ * - role 未定义, 符合条件
46
+ * - role 定义且包括当前的 userRole, 符合条件
47
+ *
48
+ * @param {object[]} nav 导航菜单数据
49
+ * @param {string} userRole 当前用户 role
50
+ * @returns 符合 role 权限的导航菜单数据
51
+ */
52
+ function filterNavByRole(nav, userRole) {
53
+ return (0, _utils.filterRecursive)(nav, (item, context) => {
54
+ const isRoleMatched = !item.role || userRole && item.role.includes(userRole);
55
+
56
+ if (!context.isLeaf) {
57
+ var _context$filteredChil;
58
+
59
+ return isRoleMatched && ((_context$filteredChil = context.filteredChildren) === null || _context$filteredChil === void 0 ? void 0 : _context$filteredChil.length);
60
+ }
61
+
62
+ return isRoleMatched;
63
+ }, 'items');
64
+ }
42
65
  /**
43
66
  * 专门用于 (composable) blocklet 的 Dashboard 组件, 解析 blocklet meta 中 section 为 dashboard 的 navigation 数据, 渲染一个 UX Dashboard
44
67
  */
68
+
69
+
45
70
  function Dashboard(_ref) {
46
- var _formattedBlocklet$na;
71
+ var _formattedBlocklet$na, _sessionCtx$session, _sessionCtx$session$u;
47
72
 
48
73
  let {
49
74
  meta
50
75
  } = _ref,
51
76
  rest = _objectWithoutProperties(_ref, _excluded);
52
77
 
78
+ const sessionCtx = (0, _react.useContext)(_Session.SessionContext);
53
79
  const {
54
80
  locale
55
81
  } = (0, _context.useLocaleContext)() || {};
@@ -72,7 +98,10 @@ function Dashboard(_ref) {
72
98
  appLogo,
73
99
  appName
74
100
  } = formattedBlocklet;
75
- let localizedNav = (0, _blocklets.getLocalizedNavigation)(formattedBlocklet === null || formattedBlocklet === void 0 ? void 0 : (_formattedBlocklet$na = formattedBlocklet.navigation) === null || _formattedBlocklet$na === void 0 ? void 0 : _formattedBlocklet$na.dashboard, locale) || [];
101
+ let localizedNav = (0, _blocklets.getLocalizedNavigation)(formattedBlocklet === null || formattedBlocklet === void 0 ? void 0 : (_formattedBlocklet$na = formattedBlocklet.navigation) === null || _formattedBlocklet$na === void 0 ? void 0 : _formattedBlocklet$na.dashboard, locale) || []; // 根据 role 筛选 nav 数据
102
+
103
+ localizedNav = filterNavByRole(localizedNav, sessionCtx === null || sessionCtx === void 0 ? void 0 : (_sessionCtx$session = sessionCtx.session) === null || _sessionCtx$session === void 0 ? void 0 : (_sessionCtx$session$u = _sessionCtx$session.user) === null || _sessionCtx$session$u === void 0 ? void 0 : _sessionCtx$session$u.role); // 将 nav 数据处理成 ux dashboard 需要的格式
104
+
76
105
  localizedNav = (0, _utils.mapRecursive)(localizedNav, item => ({
77
106
  title: item.title,
78
107
  url: item.link,
@@ -100,22 +129,19 @@ function Dashboard(_ref) {
100
129
  }, rest), {}, {
101
130
  headerProps: _objectSpread({
102
131
  brand: appName,
103
- description: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ClickToCopy.default, {
104
- unstyled: true,
105
- content: did,
106
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Address.default, {
107
- compact: true,
108
- responsive: false,
109
- copyable: false,
110
- prepend: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, {
111
- did: did,
112
- size: 16,
113
- style: {
114
- marginRight: 4
115
- }
116
- }),
117
- children: did
118
- })
132
+ description: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Address.default, {
133
+ compact: true,
134
+ responsive: false,
135
+ copyable: false,
136
+ showCopyButtonInTooltip: true,
137
+ prepend: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, {
138
+ did: did,
139
+ size: 16,
140
+ style: {
141
+ marginRight: 4
142
+ }
143
+ }),
144
+ children: did
119
145
  }),
120
146
  logo: /*#__PURE__*/(0, _jsxRuntime.jsx)("a", {
121
147
  href: _blocklets.publicPath,
@@ -216,6 +216,7 @@ function Header(_ref) {
216
216
  compact: true,
217
217
  responsive: false,
218
218
  copyable: false,
219
+ showCopyButtonInTooltip: true,
219
220
  prepend: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, {
220
221
  did: did,
221
222
  size: 16,
package/lib/blocklets.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.publicPath = exports.parseNavigation = exports.getLocalizedNavigation = exports.formatTheme = exports.formatBlockletInfo = void 0;
6
+ exports.publicPath = exports.parseNavigation = exports.getLocalizedNavigation = exports.formatTheme = exports.formatNavigation = exports.formatBlockletInfo = void 0;
7
7
 
8
8
  var _utils = require("./utils");
9
9
 
@@ -99,14 +99,35 @@ const getLocalizedNavigation = function getLocalizedNavigation(navigation) {
99
99
  });
100
100
  }, 'items');
101
101
  };
102
+ /**
103
+ * 格式化 navigation
104
+ *
105
+ * - role 统一为数组形式
106
+ */
107
+
102
108
 
103
109
  exports.getLocalizedNavigation = getLocalizedNavigation;
104
110
 
111
+ const formatNavigation = navigation => {
112
+ return (0, _utils.mapRecursive)(navigation, item => {
113
+ if (item.role) {
114
+ return _objectSpread(_objectSpread({}, item), {}, {
115
+ role: Array.isArray(item.role) ? item.role : [item.role]
116
+ });
117
+ }
118
+
119
+ return item;
120
+ }, 'items');
121
+ };
122
+
123
+ exports.formatNavigation = formatNavigation;
124
+
105
125
  const parseNavigation = navigation => {
106
126
  if (!(navigation !== null && navigation !== void 0 && navigation.length)) {
107
127
  return null;
108
128
  }
109
129
 
130
+ const formattedNav = formatNavigation(navigation);
110
131
  const sections = {
111
132
  header: [],
112
133
  footer: [],
@@ -118,7 +139,7 @@ const parseNavigation = navigation => {
118
139
  dashboard: []
119
140
  }; // 对 navigation 顶层元素按 section 分组
120
141
 
121
- navigation.forEach(item => {
142
+ formattedNav.forEach(item => {
122
143
  // item#section 为空时, 表示只存在于 header
123
144
  if (!item.section) {
124
145
  sections.header.push(item); // item 出现在指定几个 section 中 (array)
package/lib/utils.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.matchPaths = exports.matchPath = exports.mapRecursive = exports.isUrl = exports.flatRecursive = exports.countRecursive = void 0;
6
+ exports.matchPaths = exports.matchPath = exports.mapRecursive = exports.isUrl = exports.flatRecursive = exports.filterRecursive = exports.countRecursive = void 0;
7
7
 
8
8
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
9
9
 
@@ -42,11 +42,33 @@ const countRecursive = function countRecursive(array) {
42
42
  let counter = 0;
43
43
  mapRecursive(array, () => counter++, childrenKey);
44
44
  return counter;
45
- }; // "http://", "https://" 2 种情况
45
+ }; // 对有层级结构的 array 进行 filter 处理
46
+ // 因为是 DFS 遍历, 可以借助 context.filteredChildren 在过滤/保留子结的同时保持父子结构 (即使父结点不满足筛选条件)
46
47
 
47
48
 
48
49
  exports.countRecursive = countRecursive;
49
50
 
51
+ const filterRecursive = function filterRecursive(array, predicate) {
52
+ let childrenKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'children';
53
+ return array.map(item => _objectSpread({}, item)).filter(item => {
54
+ const children = item[childrenKey];
55
+
56
+ if (Array.isArray(children)) {
57
+ const filtered = filterRecursive(children, predicate, childrenKey);
58
+ item[childrenKey] = filtered !== null && filtered !== void 0 && filtered.length ? filtered : undefined;
59
+ }
60
+
61
+ const context = {
62
+ filteredChildren: item[childrenKey],
63
+ isLeaf: !(children !== null && children !== void 0 && children.length)
64
+ };
65
+ return predicate(item, context);
66
+ });
67
+ }; // "http://", "https://" 2 种情况
68
+
69
+
70
+ exports.filterRecursive = filterRecursive;
71
+
50
72
  const isUrl = str => {
51
73
  return /^https?:\/\//.test(str);
52
74
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "2.4.21",
3
+ "version": "2.4.24",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -30,8 +30,8 @@
30
30
  "url": "https://github.com/ArcBlock/ux/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@arcblock/did-connect": "^2.4.21",
34
- "@arcblock/ux": "^2.4.21",
33
+ "@arcblock/did-connect": "^2.4.24",
34
+ "@arcblock/ux": "^2.4.24",
35
35
  "@emotion/react": "^11.10.0",
36
36
  "@emotion/styled": "^11.10.0",
37
37
  "@iconify/iconify": "^2.2.1",
@@ -53,5 +53,5 @@
53
53
  "eslint-plugin-react-hooks": "^4.6.0",
54
54
  "jest": "^28.1.3"
55
55
  },
56
- "gitHead": "5989f403515c81eaaa1c1b459b1d79daf8cca317"
56
+ "gitHead": "d82c2bdd64a3a3155312fd91e6e7b40293125ad3"
57
57
  }
@@ -1,17 +1,42 @@
1
- import { useMemo } from 'react';
1
+ import { useMemo, useContext } from 'react';
2
+ import { SessionContext } from '@arcblock/did-connect/lib/Session';
2
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
4
  import UxDashboard from '@arcblock/ux/lib/Layout/dashboard';
4
- import ClickToCopy from '@arcblock/ux/lib/ClickToCopy';
5
5
  import DidAddress from '@arcblock/did-connect/lib/Address';
6
6
  import DidAvatar from '@arcblock/did-connect/lib/Avatar';
7
7
  import { blockletMetaProps } from '../types';
8
- import { mapRecursive, flatRecursive, matchPaths } from '../utils';
8
+ import { mapRecursive, flatRecursive, matchPaths, filterRecursive } from '../utils';
9
9
  import { publicPath, formatBlockletInfo, getLocalizedNavigation } from '../blocklets';
10
10
 
11
+ /**
12
+ * 根据 role 筛选 nav, 规则:
13
+ * - 如果是枝结点, 必须至少存在一个符合条件的子结点
14
+ * - role 未定义, 符合条件
15
+ * - role 定义且包括当前的 userRole, 符合条件
16
+ *
17
+ * @param {object[]} nav 导航菜单数据
18
+ * @param {string} userRole 当前用户 role
19
+ * @returns 符合 role 权限的导航菜单数据
20
+ */
21
+ function filterNavByRole(nav, userRole) {
22
+ return filterRecursive(
23
+ nav,
24
+ (item, context) => {
25
+ const isRoleMatched = !item.role || (userRole && item.role.includes(userRole));
26
+ if (!context.isLeaf) {
27
+ return isRoleMatched && context.filteredChildren?.length;
28
+ }
29
+ return isRoleMatched;
30
+ },
31
+ 'items'
32
+ );
33
+ }
34
+
11
35
  /**
12
36
  * 专门用于 (composable) blocklet 的 Dashboard 组件, 解析 blocklet meta 中 section 为 dashboard 的 navigation 数据, 渲染一个 UX Dashboard
13
37
  */
14
38
  function Dashboard({ meta, ...rest }) {
39
+ const sessionCtx = useContext(SessionContext);
15
40
  const { locale } = useLocaleContext() || {};
16
41
  const blocklet = Object.assign({}, window.blocklet, meta);
17
42
  const formattedBlocklet = useMemo(() => {
@@ -29,6 +54,9 @@ function Dashboard({ meta, ...rest }) {
29
54
 
30
55
  const { did, appLogo, appName } = formattedBlocklet;
31
56
  let localizedNav = getLocalizedNavigation(formattedBlocklet?.navigation?.dashboard, locale) || [];
57
+ // 根据 role 筛选 nav 数据
58
+ localizedNav = filterNavByRole(localizedNav, sessionCtx?.session?.user?.role);
59
+ // 将 nav 数据处理成 ux dashboard 需要的格式
32
60
  localizedNav = mapRecursive(
33
61
  localizedNav,
34
62
  (item) => ({
@@ -58,15 +86,14 @@ function Dashboard({ meta, ...rest }) {
58
86
  headerProps={{
59
87
  brand: appName,
60
88
  description: (
61
- <ClickToCopy unstyled content={did}>
62
- <DidAddress
63
- compact
64
- responsive={false}
65
- copyable={false}
66
- prepend={<DidAvatar did={did} size={16} style={{ marginRight: 4 }} />}>
67
- {did}
68
- </DidAddress>
69
- </ClickToCopy>
89
+ <DidAddress
90
+ compact
91
+ responsive={false}
92
+ copyable={false}
93
+ showCopyButtonInTooltip
94
+ prepend={<DidAvatar did={did} size={16} style={{ marginRight: 4 }} />}>
95
+ {did}
96
+ </DidAddress>
70
97
  ),
71
98
  logo: (
72
99
  <a href={publicPath}>
@@ -132,6 +132,7 @@ function Header({ meta, addons, sessionManagerProps, homeLink, theme: themeOverr
132
132
  compact
133
133
  responsive={false}
134
134
  copyable={false}
135
+ showCopyButtonInTooltip
135
136
  prepend={<DidAvatar did={did} size={16} style={{ marginRight: 4 }} />}>
136
137
  {did}
137
138
  </DidAddress>
package/src/blocklets.js CHANGED
@@ -68,11 +68,34 @@ export const getLocalizedNavigation = (navigation, locale = 'en') => {
68
68
  );
69
69
  };
70
70
 
71
+ /**
72
+ * 格式化 navigation
73
+ *
74
+ * - role 统一为数组形式
75
+ */
76
+ export const formatNavigation = (navigation) => {
77
+ return mapRecursive(
78
+ navigation,
79
+ (item) => {
80
+ if (item.role) {
81
+ return {
82
+ ...item,
83
+ role: Array.isArray(item.role) ? item.role : [item.role],
84
+ };
85
+ }
86
+ return item;
87
+ },
88
+ 'items'
89
+ );
90
+ };
91
+
71
92
  export const parseNavigation = (navigation) => {
72
93
  if (!navigation?.length) {
73
94
  return null;
74
95
  }
75
96
 
97
+ const formattedNav = formatNavigation(navigation);
98
+
76
99
  const sections = {
77
100
  header: [],
78
101
  footer: [],
@@ -85,7 +108,7 @@ export const parseNavigation = (navigation) => {
85
108
  };
86
109
 
87
110
  // 对 navigation 顶层元素按 section 分组
88
- navigation.forEach((item) => {
111
+ formattedNav.forEach((item) => {
89
112
  // item#section 为空时, 表示只存在于 header
90
113
  if (!item.section) {
91
114
  sections.header.push(item);
package/src/utils.js CHANGED
@@ -24,6 +24,22 @@ export const countRecursive = (array, childrenKey = 'children') => {
24
24
  return counter;
25
25
  };
26
26
 
27
+ // 对有层级结构的 array 进行 filter 处理
28
+ // 因为是 DFS 遍历, 可以借助 context.filteredChildren 在过滤/保留子结的同时保持父子结构 (即使父结点不满足筛选条件)
29
+ export const filterRecursive = (array, predicate, childrenKey = 'children') => {
30
+ return array
31
+ .map((item) => ({ ...item }))
32
+ .filter((item) => {
33
+ const children = item[childrenKey];
34
+ if (Array.isArray(children)) {
35
+ const filtered = filterRecursive(children, predicate, childrenKey);
36
+ item[childrenKey] = filtered?.length ? filtered : undefined;
37
+ }
38
+ const context = { filteredChildren: item[childrenKey], isLeaf: !children?.length };
39
+ return predicate(item, context);
40
+ });
41
+ };
42
+
27
43
  // "http://", "https://" 2 种情况
28
44
  export const isUrl = (str) => {
29
45
  return /^https?:\/\//.test(str);