@blocklet/list 0.8.32 → 0.8.35
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/lib/base.js +10 -3
- package/lib/components/aside.js +1 -1
- package/lib/components/filter/icon.js +1 -1
- package/lib/components/list/index.js +40 -22
- package/lib/contexts/filter.js +46 -9
- package/lib/libs/prop-types.js +0 -2
- package/lib/libs/utils.js +11 -3
- package/package.json +5 -3
- package/src/base.js +6 -2
- package/src/components/aside.js +10 -8
- package/src/components/filter/icon.js +12 -10
- package/src/components/list/index.js +40 -14
- package/src/contexts/filter.js +46 -15
- package/src/libs/prop-types.js +0 -2
- package/src/libs/utils.js +7 -2
package/lib/base.js
CHANGED
|
@@ -13,6 +13,10 @@ var _material = require("@mui/material");
|
|
|
13
13
|
|
|
14
14
|
var _Face = _interopRequireDefault(require("@mui/icons-material/Face"));
|
|
15
15
|
|
|
16
|
+
var _reactErrorBoundary = require("react-error-boundary");
|
|
17
|
+
|
|
18
|
+
var _ErrorBoundary = require("@arcblock/ux/lib/ErrorBoundary");
|
|
19
|
+
|
|
16
20
|
var _filter = require("./contexts/filter");
|
|
17
21
|
|
|
18
22
|
var _customSelect = _interopRequireDefault(require("./components/custom-select"));
|
|
@@ -36,7 +40,7 @@ function ListBase() {
|
|
|
36
40
|
|
|
37
41
|
const {
|
|
38
42
|
handleDeveloper,
|
|
39
|
-
|
|
43
|
+
finalBlockletList,
|
|
40
44
|
filters,
|
|
41
45
|
developerName,
|
|
42
46
|
handleSort,
|
|
@@ -104,8 +108,11 @@ function ListBase() {
|
|
|
104
108
|
handlePrice(null);
|
|
105
109
|
}
|
|
106
110
|
})]
|
|
107
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
108
|
-
|
|
111
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactErrorBoundary.ErrorBoundary, {
|
|
112
|
+
FallbackComponent: _ErrorBoundary.ErrorFallback,
|
|
113
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_list.default, {
|
|
114
|
+
blocklets: finalBlockletList
|
|
115
|
+
})
|
|
109
116
|
})]
|
|
110
117
|
})]
|
|
111
118
|
});
|
package/lib/components/aside.js
CHANGED
|
@@ -5,26 +5,28 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = BlockletList;
|
|
7
7
|
|
|
8
|
+
var _react = require("react");
|
|
9
|
+
|
|
8
10
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
9
11
|
|
|
10
12
|
var _styledComponents = _interopRequireDefault(require("styled-components"));
|
|
11
13
|
|
|
12
14
|
var _Empty = _interopRequireDefault(require("@arcblock/ux/lib/Empty"));
|
|
13
15
|
|
|
14
|
-
var _Alert = _interopRequireDefault(require("@arcblock/ux/lib/Alert"));
|
|
15
|
-
|
|
16
16
|
var _Box = _interopRequireDefault(require("@mui/material/Box"));
|
|
17
17
|
|
|
18
18
|
var _Grid = _interopRequireDefault(require("@mui/material/Grid"));
|
|
19
19
|
|
|
20
20
|
var _CircularProgress = _interopRequireDefault(require("@mui/material/CircularProgress"));
|
|
21
21
|
|
|
22
|
+
var _reactInfiniteScrollHook = _interopRequireDefault(require("react-infinite-scroll-hook"));
|
|
23
|
+
|
|
24
|
+
var _ErrorBoundary = require("@arcblock/ux/lib/ErrorBoundary");
|
|
25
|
+
|
|
22
26
|
var _empty = require("./empty");
|
|
23
27
|
|
|
24
28
|
var _filter = require("../../contexts/filter");
|
|
25
29
|
|
|
26
|
-
var _utils = require("../../libs/utils");
|
|
27
|
-
|
|
28
30
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
29
31
|
|
|
30
32
|
const _excluded = ["blocklets"];
|
|
@@ -52,20 +54,25 @@ function BlockletList(_ref) {
|
|
|
52
54
|
errors,
|
|
53
55
|
loadings,
|
|
54
56
|
selectedCategory,
|
|
55
|
-
|
|
57
|
+
finalBlockletList,
|
|
56
58
|
getCategoryLocale,
|
|
57
59
|
filters,
|
|
58
|
-
t
|
|
60
|
+
t,
|
|
61
|
+
hasNextPage,
|
|
62
|
+
loadMore,
|
|
63
|
+
endpoint
|
|
59
64
|
} = (0, _filter.useFilterContext)();
|
|
60
65
|
const showFilterTip = !!selectedCategory || !!filters.price;
|
|
66
|
+
const [sentryRef] = (0, _reactInfiniteScrollHook.default)({
|
|
67
|
+
loading: loadings.fetchBlockletsLoading,
|
|
68
|
+
hasNextPage,
|
|
69
|
+
onLoadMore: loadMore,
|
|
70
|
+
rootMargin: '0px 0px 400px 0px'
|
|
71
|
+
});
|
|
61
72
|
|
|
62
73
|
if (errors.fetchBlockletsError) {
|
|
63
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
64
|
-
|
|
65
|
-
variant: "icon",
|
|
66
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
67
|
-
children: (0, _utils.formatError)(errors.fetchBlockletsError)
|
|
68
|
-
})
|
|
74
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ErrorBoundary.ErrorFallback, {
|
|
75
|
+
error: new Error("Failed to fetch blocklets from ".concat(endpoint, ": ").concat(errors.fetchBlockletsError.message))
|
|
69
76
|
});
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -78,7 +85,7 @@ function BlockletList(_ref) {
|
|
|
78
85
|
});
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
if (filters.keyword && showFilterTip &&
|
|
88
|
+
if (filters.keyword && showFilterTip && finalBlockletList.length === 0) {
|
|
82
89
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(CustomEmpty, {
|
|
83
90
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_empty.EmptyTitle, {
|
|
84
91
|
primaryStart: t('blocklet.noBlockletPart1'),
|
|
@@ -91,7 +98,7 @@ function BlockletList(_ref) {
|
|
|
91
98
|
});
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
if (filters.keyword &&
|
|
101
|
+
if (filters.keyword && finalBlockletList.length === 0) {
|
|
95
102
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(CustomEmpty, {
|
|
96
103
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_empty.EmptyTitle, {
|
|
97
104
|
primaryStart: t('blocklet.noBlockletPart1'),
|
|
@@ -103,7 +110,7 @@ function BlockletList(_ref) {
|
|
|
103
110
|
});
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
if (showFilterTip &&
|
|
113
|
+
if (showFilterTip && finalBlockletList.length === 0) {
|
|
107
114
|
const categoryLocale = getCategoryLocale(selectedCategory);
|
|
108
115
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(CustomEmpty, {
|
|
109
116
|
children: [categoryLocale ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_empty.EmptyTitle, {
|
|
@@ -116,16 +123,16 @@ function BlockletList(_ref) {
|
|
|
116
123
|
});
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
if (
|
|
126
|
+
if (finalBlockletList.length === 0) {
|
|
120
127
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(CustomEmpty, {
|
|
121
128
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_empty.NoResults, {})
|
|
122
129
|
});
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
return /*#__PURE__*/(0, _jsxRuntime.
|
|
132
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(StyledGrid, _objectSpread(_objectSpread({
|
|
126
133
|
container: true
|
|
127
134
|
}, rest), {}, {
|
|
128
|
-
children: blocklets.map(blocklet => /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledGridItem, {
|
|
135
|
+
children: [blocklets.map(blocklet => /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledGridItem, {
|
|
129
136
|
item: true,
|
|
130
137
|
lg: 4,
|
|
131
138
|
md: 6,
|
|
@@ -134,9 +141,20 @@ function BlockletList(_ref) {
|
|
|
134
141
|
"data-blocklet-did": blocklet.did,
|
|
135
142
|
children: blockletRender({
|
|
136
143
|
blocklet,
|
|
137
|
-
blocklets:
|
|
144
|
+
blocklets: finalBlockletList
|
|
145
|
+
})
|
|
146
|
+
}, blocklet.did)), hasNextPage && /*#__PURE__*/(0, _jsxRuntime.jsx)(StyledGridItem, {
|
|
147
|
+
item: true,
|
|
148
|
+
md: 12,
|
|
149
|
+
sm: 12,
|
|
150
|
+
xs: 12,
|
|
151
|
+
ref: sentryRef,
|
|
152
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Box.default, {
|
|
153
|
+
display: "flex",
|
|
154
|
+
justifyContent: "center",
|
|
155
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_CircularProgress.default, {})
|
|
138
156
|
})
|
|
139
|
-
}
|
|
157
|
+
})]
|
|
140
158
|
}));
|
|
141
159
|
}
|
|
142
160
|
|
|
@@ -148,10 +166,10 @@ const StyledGrid = (0, _styledComponents.default)(_Grid.default).withConfig({
|
|
|
148
166
|
displayName: "list__StyledGrid",
|
|
149
167
|
componentId: "sc-1guvpon-0"
|
|
150
168
|
})(["&.MuiGrid-root{width:auto;margin:0 -16px;}"]);
|
|
151
|
-
const StyledGridItem = (0, _styledComponents.default)(_Grid.default).withConfig({
|
|
169
|
+
const StyledGridItem = /*#__PURE__*/(0, _react.memo)((0, _styledComponents.default)(_Grid.default).withConfig({
|
|
152
170
|
displayName: "list__StyledGridItem",
|
|
153
171
|
componentId: "sc-1guvpon-1"
|
|
154
|
-
})(["@media (max-width:", "px){&.MuiGrid-item{padding-bottom:0px;}}@media (min-width:", "px){&.MuiGrid-item{margin-bottom:", ";}}"], props => props.theme.breakpoints.values.sm, props => props.theme.breakpoints.values.sm, props => props.theme.spacing(2));
|
|
172
|
+
})(["@media (max-width:", "px){&.MuiGrid-item{padding-bottom:0px;}}@media (min-width:", "px){&.MuiGrid-item{margin-bottom:", ";}}"], props => props.theme.breakpoints.values.sm, props => props.theme.breakpoints.values.sm, props => props.theme.spacing(2)));
|
|
155
173
|
const CustomEmpty = (0, _styledComponents.default)(_Empty.default).withConfig({
|
|
156
174
|
displayName: "list__CustomEmpty",
|
|
157
175
|
componentId: "sc-1guvpon-2"
|
package/lib/contexts/filter.js
CHANGED
|
@@ -17,6 +17,8 @@ var _orderBy = _interopRequireDefault(require("lodash/orderBy"));
|
|
|
17
17
|
|
|
18
18
|
var _axios = _interopRequireDefault(require("axios"));
|
|
19
19
|
|
|
20
|
+
var _isArray = _interopRequireDefault(require("lodash/isArray"));
|
|
21
|
+
|
|
20
22
|
var _utils = require("../libs/utils");
|
|
21
23
|
|
|
22
24
|
var _locale = _interopRequireDefault(require("../assets/locale"));
|
|
@@ -44,7 +46,6 @@ function FilterProvider(_ref) {
|
|
|
44
46
|
let {
|
|
45
47
|
filters,
|
|
46
48
|
children,
|
|
47
|
-
baseUrl,
|
|
48
49
|
endpoint,
|
|
49
50
|
locale,
|
|
50
51
|
blockletRender,
|
|
@@ -63,9 +64,14 @@ function FilterProvider(_ref) {
|
|
|
63
64
|
run: fetchBlocklets
|
|
64
65
|
} = (0, _ahooks.useRequest)(async () => {
|
|
65
66
|
const {
|
|
66
|
-
data
|
|
67
|
+
data
|
|
67
68
|
} = await storeApi.get('/api/blocklets.json');
|
|
68
|
-
|
|
69
|
+
|
|
70
|
+
if (!(0, _isArray.default)(data)) {
|
|
71
|
+
throw new Error('/api/blocklets.json response is not array');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return data;
|
|
69
75
|
}, {
|
|
70
76
|
initialData: [],
|
|
71
77
|
manual: true
|
|
@@ -77,13 +83,23 @@ function FilterProvider(_ref) {
|
|
|
77
83
|
run: fetchCategories
|
|
78
84
|
} = (0, _ahooks.useRequest)(async () => {
|
|
79
85
|
const {
|
|
80
|
-
data
|
|
86
|
+
data
|
|
81
87
|
} = await storeApi.get('/api/blocklets/categories');
|
|
82
|
-
|
|
88
|
+
|
|
89
|
+
if (!(0, _isArray.default)(data)) {
|
|
90
|
+
throw new Error('/api/blocklets/categories response is not array');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return data;
|
|
83
94
|
}, {
|
|
84
95
|
initialData: [],
|
|
85
96
|
manual: true
|
|
86
97
|
});
|
|
98
|
+
const paginateState = (0, _ahooks.useReactive)({
|
|
99
|
+
currentPage: 1,
|
|
100
|
+
pageSize: (0, _utils.isMobileScreen)() ? 10 : 18,
|
|
101
|
+
defaultCurrentPage: 1
|
|
102
|
+
});
|
|
87
103
|
|
|
88
104
|
const finalFilters = _objectSpread({
|
|
89
105
|
sortBy: 'popularity',
|
|
@@ -142,6 +158,10 @@ function FilterProvider(_ref) {
|
|
|
142
158
|
|
|
143
159
|
return (0, _orderBy.default)(blocklets, [sortMap[finalFilters.sortBy]], [finalFilters.sortDirection]);
|
|
144
160
|
}, [allBlocklets, finalFilters]);
|
|
161
|
+
const finalBlockletList = (0, _react.useMemo)(() => {
|
|
162
|
+
// 前端分页 currentPage 当前页数 pageSize 每页条数
|
|
163
|
+
return blockletList.slice((paginateState.defaultCurrentPage - 1) * paginateState.pageSize, paginateState.currentPage * paginateState.pageSize);
|
|
164
|
+
}, [blockletList, paginateState]);
|
|
145
165
|
const categoryList = (0, _react.useMemo)(() => {
|
|
146
166
|
const list = categoryState.data || []; // 分类按照名称排序
|
|
147
167
|
|
|
@@ -169,16 +189,16 @@ function FilterProvider(_ref) {
|
|
|
169
189
|
fetchCategoriesLoading
|
|
170
190
|
},
|
|
171
191
|
endpoint,
|
|
172
|
-
|
|
192
|
+
finalBlockletList,
|
|
173
193
|
t: translate,
|
|
174
194
|
filters: finalFilters,
|
|
175
195
|
selectedCategory,
|
|
176
196
|
categoryList,
|
|
177
|
-
baseUrl,
|
|
178
197
|
blockletRender,
|
|
179
198
|
locale,
|
|
180
199
|
categoryOptions,
|
|
181
200
|
priceOptions,
|
|
201
|
+
hasNextPage: blockletList.length >= paginateState.pageSize * paginateState.currentPage,
|
|
182
202
|
handleSort: sort => {
|
|
183
203
|
const changeData = _objectSpread(_objectSpread({}, finalFilters), {}, {
|
|
184
204
|
sortBy: sort,
|
|
@@ -221,6 +241,18 @@ function FilterProvider(_ref) {
|
|
|
221
241
|
|
|
222
242
|
onFilterChange(changeData);
|
|
223
243
|
},
|
|
244
|
+
handlePage: page => {
|
|
245
|
+
const changeData = _objectSpread(_objectSpread({}, finalFilters), {}, {
|
|
246
|
+
currentPage: page
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
onFilterChange(changeData);
|
|
250
|
+
},
|
|
251
|
+
loadMore: () => {
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
paginateState.currentPage += 1;
|
|
254
|
+
}, 1000);
|
|
255
|
+
},
|
|
224
256
|
getCategoryLocale: category => {
|
|
225
257
|
if (!category) return null;
|
|
226
258
|
let result = null;
|
|
@@ -233,10 +265,15 @@ function FilterProvider(_ref) {
|
|
|
233
265
|
return result;
|
|
234
266
|
},
|
|
235
267
|
|
|
268
|
+
get allBlocklets() {
|
|
269
|
+
return allBlocklets || [];
|
|
270
|
+
},
|
|
271
|
+
|
|
236
272
|
get developerName() {
|
|
237
|
-
var
|
|
273
|
+
var _blocklets$find, _blocklets$find$owner;
|
|
238
274
|
|
|
239
|
-
|
|
275
|
+
const blocklets = allBlocklets || [];
|
|
276
|
+
return ((_blocklets$find = blocklets.find(blocklet => blocklet.owner.did === finalFilters.developer)) === null || _blocklets$find === void 0 ? void 0 : (_blocklets$find$owner = _blocklets$find.owner) === null || _blocklets$find$owner === void 0 ? void 0 : _blocklets$find$owner.name) || '';
|
|
240
277
|
}
|
|
241
278
|
|
|
242
279
|
};
|
package/lib/libs/prop-types.js
CHANGED
|
@@ -22,12 +22,10 @@ const propTypes = {
|
|
|
22
22
|
endpoint: _propTypes.default.string.isRequired,
|
|
23
23
|
blockletRender: _propTypes.default.func.isRequired,
|
|
24
24
|
onFilterChange: _propTypes.default.func,
|
|
25
|
-
baseUrl: _propTypes.default.string,
|
|
26
25
|
locale: _propTypes.default.oneOf(['zh', 'en'])
|
|
27
26
|
};
|
|
28
27
|
exports.propTypes = propTypes;
|
|
29
28
|
const defaultProps = {
|
|
30
|
-
baseUrl: null,
|
|
31
29
|
locale: 'zh',
|
|
32
30
|
filters: {},
|
|
33
31
|
onFilterChange: () => {},
|
package/lib/libs/utils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.urlStringify = exports.replaceTranslate = exports.removeUndefined = exports.getStoreDetail = exports.getSortOptions = exports.getPrices = exports.getCategoryOptions = exports.getCategories = exports.formatLogoPath = exports.formatError = exports.filterBlockletByPrice = void 0;
|
|
6
|
+
exports.urlStringify = exports.replaceTranslate = exports.removeUndefined = exports.isMobileScreen = exports.getStoreDetail = exports.getSortOptions = exports.getPrices = exports.getCategoryOptions = exports.getCategories = exports.formatLogoPath = exports.formatError = exports.filterBlockletByPrice = void 0;
|
|
7
7
|
|
|
8
8
|
var _urlJoin = _interopRequireDefault(require("url-join"));
|
|
9
9
|
|
|
@@ -68,7 +68,9 @@ const getCategoryOptions = function getCategoryOptions() {
|
|
|
68
68
|
|
|
69
69
|
exports.getCategoryOptions = getCategoryOptions;
|
|
70
70
|
|
|
71
|
-
const getCategories = (
|
|
71
|
+
const getCategories = function getCategories() {
|
|
72
|
+
let list = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
73
|
+
let developerDid = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
72
74
|
const filterList = list.filter(item => developerDid ? item.owner.did === developerDid : true);
|
|
73
75
|
const Categories = filterList.map(item => item.category);
|
|
74
76
|
const res = new Map();
|
|
@@ -157,4 +159,10 @@ const urlStringify = obj => {
|
|
|
157
159
|
return new URLSearchParams(removeUndefined(obj)).toString();
|
|
158
160
|
};
|
|
159
161
|
|
|
160
|
-
exports.urlStringify = urlStringify;
|
|
162
|
+
exports.urlStringify = urlStringify;
|
|
163
|
+
|
|
164
|
+
const isMobileScreen = () => {
|
|
165
|
+
return window.innerWidth <= 600;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
exports.isMobileScreen = isMobileScreen;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/list",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.35",
|
|
4
4
|
"description": "Common ux components of blocklet",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"react": ">=18.1.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@arcblock/ux": "^2.1.
|
|
41
|
+
"@arcblock/ux": "^2.1.21",
|
|
42
42
|
"@emotion/react": "^11.9.0",
|
|
43
43
|
"@emotion/styled": "^11.8.1",
|
|
44
44
|
"@mui/icons-material": "^5.6.2",
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
"flat": "^5.0.2",
|
|
48
48
|
"lodash": "^4.17.21",
|
|
49
49
|
"prop-types": "^15.7.2",
|
|
50
|
+
"react-error-boundary": "^3.1.4",
|
|
51
|
+
"react-infinite-scroll-hook": "^4.0.3",
|
|
50
52
|
"styled-components": "5.3.5",
|
|
51
53
|
"url-join": "^4.0.1"
|
|
52
54
|
},
|
|
@@ -62,5 +64,5 @@
|
|
|
62
64
|
"eslint": "^8.16.0",
|
|
63
65
|
"prettier": "^2.6.2"
|
|
64
66
|
},
|
|
65
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "f6a52e3c84e6eb4cd5625f688a55d7da1b477740"
|
|
66
68
|
}
|
package/src/base.js
CHANGED
|
@@ -2,6 +2,8 @@ import styled from 'styled-components';
|
|
|
2
2
|
import SortIcon from '@mui/icons-material/Sort';
|
|
3
3
|
import { Box, Hidden } from '@mui/material';
|
|
4
4
|
import FaceIcon from '@mui/icons-material/Face';
|
|
5
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
6
|
+
import { ErrorFallback } from '@arcblock/ux/lib/ErrorBoundary';
|
|
5
7
|
|
|
6
8
|
import { useFilterContext } from './contexts/filter';
|
|
7
9
|
import CustomSelect from './components/custom-select';
|
|
@@ -14,7 +16,7 @@ import Search from './components/search';
|
|
|
14
16
|
function ListBase() {
|
|
15
17
|
const {
|
|
16
18
|
handleDeveloper,
|
|
17
|
-
|
|
19
|
+
finalBlockletList,
|
|
18
20
|
filters,
|
|
19
21
|
developerName,
|
|
20
22
|
handleSort,
|
|
@@ -76,7 +78,9 @@ function ListBase() {
|
|
|
76
78
|
}}
|
|
77
79
|
/>
|
|
78
80
|
</Box>
|
|
79
|
-
<
|
|
81
|
+
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
|
82
|
+
<BlockletList blocklets={finalBlockletList} />
|
|
83
|
+
</ErrorBoundary>
|
|
80
84
|
</StyledMin>
|
|
81
85
|
</Box>
|
|
82
86
|
);
|
package/src/components/aside.js
CHANGED
|
@@ -12,14 +12,16 @@ function Aside() {
|
|
|
12
12
|
<div>
|
|
13
13
|
<FilterGroup title={t('common.price')} options={priceOptions} value={filters.price} onChange={handlePrice} />
|
|
14
14
|
</div>
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
{categoryOptions.length > 0 && (
|
|
16
|
+
<div style={{ marginTop: '16px' }}>
|
|
17
|
+
<FilterGroup
|
|
18
|
+
title={t('common.category')}
|
|
19
|
+
options={categoryOptions}
|
|
20
|
+
value={selectedCategory}
|
|
21
|
+
onChange={handleCategory}
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
)}
|
|
23
25
|
</StyledAside>
|
|
24
26
|
);
|
|
25
27
|
}
|
|
@@ -36,16 +36,18 @@ function FilterIcon() {
|
|
|
36
36
|
handelChange('price', v);
|
|
37
37
|
}}
|
|
38
38
|
/>
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
{categoryOptions.length > 0 && (
|
|
40
|
+
<div style={{ marginTop: '16px' }}>
|
|
41
|
+
<FilterGroup
|
|
42
|
+
title={t('common.category')}
|
|
43
|
+
options={categoryOptions}
|
|
44
|
+
value={selectedCategory}
|
|
45
|
+
onChange={(v) => {
|
|
46
|
+
handelChange('category', v);
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
49
51
|
</Dialog>
|
|
50
52
|
</StyledDiv>
|
|
51
53
|
);
|
|
@@ -1,26 +1,45 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
1
2
|
import PropTypes from 'prop-types';
|
|
2
3
|
import styled from 'styled-components';
|
|
3
4
|
import Empty from '@arcblock/ux/lib/Empty';
|
|
4
|
-
import Alert from '@arcblock/ux/lib/Alert';
|
|
5
5
|
import Box from '@mui/material/Box';
|
|
6
6
|
import Grid from '@mui/material/Grid';
|
|
7
7
|
import CircularProgress from '@mui/material/CircularProgress';
|
|
8
|
+
import useInfiniteScroll from 'react-infinite-scroll-hook';
|
|
9
|
+
import { ErrorFallback } from '@arcblock/ux/lib/ErrorBoundary';
|
|
8
10
|
|
|
9
11
|
import { NoResults, EmptyTitle, NoResultsTips } from './empty';
|
|
10
12
|
import { useFilterContext } from '../../contexts/filter';
|
|
11
|
-
import { formatError } from '../../libs/utils';
|
|
12
13
|
|
|
13
14
|
export default function BlockletList({ blocklets, ...rest }) {
|
|
14
|
-
const {
|
|
15
|
-
|
|
15
|
+
const {
|
|
16
|
+
blockletRender,
|
|
17
|
+
errors,
|
|
18
|
+
loadings,
|
|
19
|
+
selectedCategory,
|
|
20
|
+
finalBlockletList,
|
|
21
|
+
getCategoryLocale,
|
|
22
|
+
filters,
|
|
23
|
+
t,
|
|
24
|
+
hasNextPage,
|
|
25
|
+
loadMore,
|
|
26
|
+
endpoint,
|
|
27
|
+
} = useFilterContext();
|
|
16
28
|
|
|
17
29
|
const showFilterTip = !!selectedCategory || !!filters.price;
|
|
18
30
|
|
|
31
|
+
const [sentryRef] = useInfiniteScroll({
|
|
32
|
+
loading: loadings.fetchBlockletsLoading,
|
|
33
|
+
hasNextPage,
|
|
34
|
+
onLoadMore: loadMore,
|
|
35
|
+
rootMargin: '0px 0px 400px 0px',
|
|
36
|
+
});
|
|
37
|
+
|
|
19
38
|
if (errors.fetchBlockletsError) {
|
|
20
39
|
return (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
<ErrorFallback
|
|
41
|
+
error={new Error(`Failed to fetch blocklets from ${endpoint}: ${errors.fetchBlockletsError.message}`)}
|
|
42
|
+
/>
|
|
24
43
|
);
|
|
25
44
|
}
|
|
26
45
|
if (loadings.fetchBlockletsLoading) {
|
|
@@ -30,7 +49,7 @@ export default function BlockletList({ blocklets, ...rest }) {
|
|
|
30
49
|
</Box>
|
|
31
50
|
);
|
|
32
51
|
}
|
|
33
|
-
if (filters.keyword && showFilterTip &&
|
|
52
|
+
if (filters.keyword && showFilterTip && finalBlockletList.length === 0) {
|
|
34
53
|
return (
|
|
35
54
|
<CustomEmpty>
|
|
36
55
|
<EmptyTitle
|
|
@@ -42,7 +61,7 @@ export default function BlockletList({ blocklets, ...rest }) {
|
|
|
42
61
|
</CustomEmpty>
|
|
43
62
|
);
|
|
44
63
|
}
|
|
45
|
-
if (filters.keyword &&
|
|
64
|
+
if (filters.keyword && finalBlockletList.length === 0) {
|
|
46
65
|
return (
|
|
47
66
|
<CustomEmpty>
|
|
48
67
|
<EmptyTitle
|
|
@@ -54,7 +73,7 @@ export default function BlockletList({ blocklets, ...rest }) {
|
|
|
54
73
|
</CustomEmpty>
|
|
55
74
|
);
|
|
56
75
|
}
|
|
57
|
-
if (showFilterTip &&
|
|
76
|
+
if (showFilterTip && finalBlockletList.length === 0) {
|
|
58
77
|
const categoryLocale = getCategoryLocale(selectedCategory);
|
|
59
78
|
return (
|
|
60
79
|
<CustomEmpty>
|
|
@@ -71,7 +90,7 @@ export default function BlockletList({ blocklets, ...rest }) {
|
|
|
71
90
|
</CustomEmpty>
|
|
72
91
|
);
|
|
73
92
|
}
|
|
74
|
-
if (
|
|
93
|
+
if (finalBlockletList.length === 0) {
|
|
75
94
|
return (
|
|
76
95
|
<CustomEmpty>
|
|
77
96
|
<NoResults />
|
|
@@ -83,9 +102,16 @@ export default function BlockletList({ blocklets, ...rest }) {
|
|
|
83
102
|
<StyledGrid container {...rest}>
|
|
84
103
|
{blocklets.map((blocklet) => (
|
|
85
104
|
<StyledGridItem item lg={4} md={6} sm={6} xs={12} key={blocklet.did} data-blocklet-did={blocklet.did}>
|
|
86
|
-
{blockletRender({ blocklet, blocklets:
|
|
105
|
+
{blockletRender({ blocklet, blocklets: finalBlockletList })}
|
|
87
106
|
</StyledGridItem>
|
|
88
107
|
))}
|
|
108
|
+
{hasNextPage && (
|
|
109
|
+
<StyledGridItem item md={12} sm={12} xs={12} ref={sentryRef}>
|
|
110
|
+
<Box display="flex" justifyContent="center">
|
|
111
|
+
<CircularProgress />
|
|
112
|
+
</Box>
|
|
113
|
+
</StyledGridItem>
|
|
114
|
+
)}
|
|
89
115
|
</StyledGrid>
|
|
90
116
|
);
|
|
91
117
|
}
|
|
@@ -103,7 +129,7 @@ const StyledGrid = styled(Grid)`
|
|
|
103
129
|
}
|
|
104
130
|
`;
|
|
105
131
|
|
|
106
|
-
const StyledGridItem = styled(Grid)`
|
|
132
|
+
const StyledGridItem = memo(styled(Grid)`
|
|
107
133
|
@media (max-width: ${(props) => props.theme.breakpoints.values.sm}px) {
|
|
108
134
|
&.MuiGrid-item {
|
|
109
135
|
padding-bottom: 0px;
|
|
@@ -114,7 +140,7 @@ const StyledGridItem = styled(Grid)`
|
|
|
114
140
|
margin-bottom: ${(props) => props.theme.spacing(2)};
|
|
115
141
|
}
|
|
116
142
|
}
|
|
117
|
-
|
|
143
|
+
`);
|
|
118
144
|
const CustomEmpty = styled(Empty)`
|
|
119
145
|
text-align: center;
|
|
120
146
|
.primary {
|
package/src/contexts/filter.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { useContext, createContext, useMemo, useEffect } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { useRequest } from 'ahooks';
|
|
3
|
+
import { useRequest, useReactive } from 'ahooks';
|
|
4
4
|
import orderBy from 'lodash/orderBy';
|
|
5
5
|
import axios from 'axios';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import isArray from 'lodash/isArray';
|
|
7
|
+
import {
|
|
8
|
+
getCategories,
|
|
9
|
+
filterBlockletByPrice,
|
|
10
|
+
replaceTranslate,
|
|
11
|
+
getPrices,
|
|
12
|
+
getCategoryOptions,
|
|
13
|
+
isMobileScreen,
|
|
14
|
+
} from '../libs/utils';
|
|
9
15
|
import translations from '../assets/locale';
|
|
10
16
|
import { propTypes, defaultProps } from '../libs/prop-types';
|
|
11
17
|
|
|
12
18
|
const Filter = createContext({});
|
|
13
19
|
const { Provider, Consumer } = Filter;
|
|
14
20
|
|
|
15
|
-
function FilterProvider({ filters, children,
|
|
21
|
+
function FilterProvider({ filters, children, endpoint, locale, blockletRender, onFilterChange, extraFilter }) {
|
|
16
22
|
const storeApi = axios.create({
|
|
17
23
|
baseURL: endpoint,
|
|
18
24
|
});
|
|
@@ -23,8 +29,11 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
23
29
|
run: fetchBlocklets,
|
|
24
30
|
} = useRequest(
|
|
25
31
|
async () => {
|
|
26
|
-
const { data
|
|
27
|
-
|
|
32
|
+
const { data } = await storeApi.get('/api/blocklets.json');
|
|
33
|
+
if (!isArray(data)) {
|
|
34
|
+
throw new Error('/api/blocklets.json response is not array');
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
28
37
|
},
|
|
29
38
|
{ initialData: [], manual: true }
|
|
30
39
|
);
|
|
@@ -36,12 +45,16 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
36
45
|
run: fetchCategories,
|
|
37
46
|
} = useRequest(
|
|
38
47
|
async () => {
|
|
39
|
-
const { data
|
|
40
|
-
|
|
48
|
+
const { data } = await storeApi.get('/api/blocklets/categories');
|
|
49
|
+
if (!isArray(data)) {
|
|
50
|
+
throw new Error('/api/blocklets/categories response is not array');
|
|
51
|
+
}
|
|
52
|
+
return data;
|
|
41
53
|
},
|
|
42
54
|
{ initialData: [], manual: true }
|
|
43
55
|
);
|
|
44
56
|
|
|
57
|
+
const paginateState = useReactive({ currentPage: 1, pageSize: isMobileScreen() ? 10 : 18, defaultCurrentPage: 1 });
|
|
45
58
|
const finalFilters = { sortBy: 'popularity', sortDirection: 'desc', ...filters };
|
|
46
59
|
const selectedCategory = finalFilters.category;
|
|
47
60
|
const hasDeveloperFilter = !!finalFilters.developer;
|
|
@@ -59,7 +72,6 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
59
72
|
popularity: sortByPopularity,
|
|
60
73
|
publishAt: sortByPublish,
|
|
61
74
|
};
|
|
62
|
-
|
|
63
75
|
let blocklets = allBlocklets || [];
|
|
64
76
|
// 按照付费/免费筛选
|
|
65
77
|
blocklets = filterBlockletByPrice(blocklets, finalFilters.price);
|
|
@@ -84,6 +96,14 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
84
96
|
return orderBy(blocklets, [sortMap[finalFilters.sortBy]], [finalFilters.sortDirection]);
|
|
85
97
|
}, [allBlocklets, finalFilters]);
|
|
86
98
|
|
|
99
|
+
const finalBlockletList = useMemo(() => {
|
|
100
|
+
// 前端分页 currentPage 当前页数 pageSize 每页条数
|
|
101
|
+
return blockletList.slice(
|
|
102
|
+
(paginateState.defaultCurrentPage - 1) * paginateState.pageSize,
|
|
103
|
+
paginateState.currentPage * paginateState.pageSize
|
|
104
|
+
);
|
|
105
|
+
}, [blockletList, paginateState]);
|
|
106
|
+
|
|
87
107
|
const categoryList = useMemo(() => {
|
|
88
108
|
const list = categoryState.data || [];
|
|
89
109
|
// 分类按照名称排序
|
|
@@ -98,24 +118,22 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
98
118
|
|
|
99
119
|
return replaceTranslate(translations[locale][key], data);
|
|
100
120
|
};
|
|
101
|
-
|
|
102
121
|
const categoryOptions = useMemo(() => getCategoryOptions(categoryList, locale), [categoryList, locale]);
|
|
103
122
|
const priceOptions = getPrices(translate);
|
|
104
|
-
|
|
105
123
|
const filterStore = {
|
|
106
124
|
errors: { fetchBlockletsError, fetchCategoriesError },
|
|
107
125
|
loadings: { fetchBlockletsLoading, fetchCategoriesLoading },
|
|
108
126
|
endpoint,
|
|
109
|
-
|
|
127
|
+
finalBlockletList,
|
|
110
128
|
t: translate,
|
|
111
129
|
filters: finalFilters,
|
|
112
130
|
selectedCategory,
|
|
113
131
|
categoryList,
|
|
114
|
-
baseUrl,
|
|
115
132
|
blockletRender,
|
|
116
133
|
locale,
|
|
117
134
|
categoryOptions,
|
|
118
135
|
priceOptions,
|
|
136
|
+
hasNextPage: blockletList.length >= paginateState.pageSize * paginateState.currentPage,
|
|
119
137
|
handleSort: (sort) => {
|
|
120
138
|
const changeData = {
|
|
121
139
|
...finalFilters,
|
|
@@ -147,6 +165,15 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
147
165
|
const changeData = { ...finalFilters, developer: developer || undefined };
|
|
148
166
|
onFilterChange(changeData);
|
|
149
167
|
},
|
|
168
|
+
handlePage: (page) => {
|
|
169
|
+
const changeData = { ...finalFilters, currentPage: page };
|
|
170
|
+
onFilterChange(changeData);
|
|
171
|
+
},
|
|
172
|
+
loadMore: () => {
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
paginateState.currentPage += 1;
|
|
175
|
+
}, 1000);
|
|
176
|
+
},
|
|
150
177
|
getCategoryLocale: (category) => {
|
|
151
178
|
if (!category) return null;
|
|
152
179
|
let result = null;
|
|
@@ -156,8 +183,12 @@ function FilterProvider({ filters, children, baseUrl, endpoint, locale, blocklet
|
|
|
156
183
|
}
|
|
157
184
|
return result;
|
|
158
185
|
},
|
|
186
|
+
get allBlocklets() {
|
|
187
|
+
return allBlocklets || [];
|
|
188
|
+
},
|
|
159
189
|
get developerName() {
|
|
160
|
-
|
|
190
|
+
const blocklets = allBlocklets || [];
|
|
191
|
+
return blocklets.find((blocklet) => blocklet.owner.did === finalFilters.developer)?.owner?.name || '';
|
|
161
192
|
},
|
|
162
193
|
};
|
|
163
194
|
|
package/src/libs/prop-types.js
CHANGED
|
@@ -13,12 +13,10 @@ const propTypes = {
|
|
|
13
13
|
endpoint: PropTypes.string.isRequired,
|
|
14
14
|
blockletRender: PropTypes.func.isRequired,
|
|
15
15
|
onFilterChange: PropTypes.func,
|
|
16
|
-
baseUrl: PropTypes.string,
|
|
17
16
|
locale: PropTypes.oneOf(['zh', 'en']),
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
const defaultProps = {
|
|
21
|
-
baseUrl: null,
|
|
22
20
|
locale: 'zh',
|
|
23
21
|
filters: {},
|
|
24
22
|
onFilterChange: () => {},
|
package/src/libs/utils.js
CHANGED
|
@@ -5,10 +5,10 @@ const isFreeBlocklet = (meta) => {
|
|
|
5
5
|
if (!meta.payment) {
|
|
6
6
|
return true;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
8
|
const priceList = (meta.payment.price || []).map((x) => x.value || 0);
|
|
10
9
|
return priceList.every((x) => x === 0);
|
|
11
10
|
};
|
|
11
|
+
|
|
12
12
|
const getSortOptions = (t) => {
|
|
13
13
|
return [
|
|
14
14
|
{
|
|
@@ -44,7 +44,7 @@ const getCategoryOptions = (list = [], locale = 'en') => {
|
|
|
44
44
|
* @param {*} developerDid
|
|
45
45
|
* @returns
|
|
46
46
|
*/
|
|
47
|
-
const getCategories = (list, developerDid) => {
|
|
47
|
+
const getCategories = (list = [], developerDid = null) => {
|
|
48
48
|
const filterList = list.filter((item) => (developerDid ? item.owner.did === developerDid : true));
|
|
49
49
|
const Categories = filterList.map((item) => item.category);
|
|
50
50
|
const res = new Map();
|
|
@@ -106,6 +106,10 @@ const urlStringify = (obj) => {
|
|
|
106
106
|
return new URLSearchParams(removeUndefined(obj)).toString();
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
+
const isMobileScreen = () => {
|
|
110
|
+
return window.innerWidth <= 600;
|
|
111
|
+
};
|
|
112
|
+
|
|
109
113
|
export {
|
|
110
114
|
getSortOptions,
|
|
111
115
|
getPrices,
|
|
@@ -118,4 +122,5 @@ export {
|
|
|
118
122
|
removeUndefined,
|
|
119
123
|
urlStringify,
|
|
120
124
|
getCategoryOptions,
|
|
125
|
+
isMobileScreen,
|
|
121
126
|
};
|