@arcblock/ux 2.10.13 → 2.10.15

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,7 @@
1
+ {
2
+ "address": "zjdvx8aWRFo96Y974NDRDgynnRKVJyxeRpmv",
3
+ "display": {
4
+ "content": "https://storage.staging.abtnet.io/app/resolve/display",
5
+ "type": "url"
6
+ }
7
+ }
@@ -7,7 +7,6 @@ import get from 'lodash/get';
7
7
  import pako from 'pako';
8
8
  import base64 from 'base64-url';
9
9
  import isSvg from 'is-svg';
10
- import { ReactSVG } from 'react-svg';
11
10
  import noop from 'lodash/noop';
12
11
  import AspectRatioContainer from './aspect-ratio-container';
13
12
  import ImgSvgEmbedder from './svg-embedder/img';
@@ -65,6 +64,7 @@ function NFTDisplay({
65
64
  checkSvg,
66
65
  minimumLoadingTime,
67
66
  onCompleted,
67
+ imageFilter,
68
68
  ...rest
69
69
  }) {
70
70
  const wrapRoot = children => /*#__PURE__*/_jsx(Root, {
@@ -96,16 +96,72 @@ function NFTDisplay({
96
96
  // 首次加载, 对于 url type 的情况, loading 为 true
97
97
  const [state, setState] = useState({
98
98
  loading: isUrlType,
99
- error: false
99
+ error: false,
100
+ loadingUrlType: true,
101
+ urlType: null
100
102
  });
101
103
  const [minimumLoadingReady, setMinimumLoadingReady] = useState(minimumLoadingTime <= 0);
102
104
  // console.log('[debug] render', {type, minimumLoadingTime}, JSON.stringify(state))
103
105
 
106
+ // assemble the complete url
107
+ const getFullContentUrl = ({
108
+ useImageFilter = false,
109
+ t
110
+ } = {}) => {
111
+ const urlObj = new URL(content);
112
+ if (!urlObj.searchParams.has('assetId')) {
113
+ urlObj.searchParams.append('assetId', address);
114
+ }
115
+ if (!urlObj.searchParams.has('vcId') && vcId) {
116
+ urlObj.searchParams.append('vcId', vcId);
117
+ }
118
+
119
+ // image filter
120
+ if (useImageFilter && imageFilter) {
121
+ Object.entries(imageFilter).forEach(([key, value]) => {
122
+ urlObj.searchParams.append(key, value);
123
+ });
124
+ }
125
+
126
+ // other params
127
+ if (t) {
128
+ urlObj.searchParams.append('t', t);
129
+ }
130
+ return urlObj.href;
131
+ };
132
+
133
+ // get url content type
134
+ const getUrlContentType = async () => {
135
+ try {
136
+ const response = await fetch(getFullContentUrl({
137
+ useImageFilter: false,
138
+ t: 'nftdisplay'
139
+ }), {
140
+ method: 'HEAD'
141
+ });
142
+ const contentType = response.headers.get('Content-Type');
143
+ setState({
144
+ ...state,
145
+ loadingUrlType: false,
146
+ urlType: contentType
147
+ });
148
+ } catch (error) {
149
+ console.error('Failed to fetch url content type', error);
150
+ setState({
151
+ ...state,
152
+ loadingUrlType: false,
153
+ urlType: null
154
+ });
155
+ }
156
+ };
104
157
  useEffect(() => {
105
158
  let timer;
106
159
  if (minimumLoadingTime > 0) {
107
160
  timer = setTimeout(() => setMinimumLoadingReady(true), minimumLoadingTime);
108
161
  }
162
+ if (type === 'url') {
163
+ getUrlContentType();
164
+ }
109
165
  return () => clearTimeout(timer);
110
166
  // eslint-disable-next-line react-hooks/exhaustive-deps
111
167
  }, []);
@@ -120,69 +176,73 @@ function NFTDisplay({
120
176
  if (state.error) {
121
177
  throw new Error('Failed to render NFT Display.');
122
178
  }
179
+ const onLoad = () => {
180
+ if (state.loading) {
181
+ setState({
182
+ ...state,
183
+ loading: false
184
+ });
185
+ }
186
+ };
187
+ const onError = () => {
188
+ setState({
189
+ ...state,
190
+ error: true,
191
+ loading: false
192
+ });
193
+ };
194
+
195
+ // render image
196
+ const renderImg = () => {
197
+ const url = getFullContentUrl({
198
+ useImageFilter: true
199
+ });
200
+ return /*#__PURE__*/_jsx("img", {
201
+ src: url,
202
+ onError: onError,
203
+ onLoad: onLoad,
204
+ alt: "NFT Display",
205
+ style: {
206
+ width: 'auto',
207
+ height: 'auto'
208
+ }
209
+ });
210
+ };
211
+
212
+ // render object
213
+ const renderObject = () => {
214
+ const objectType = state.urlType || 'image/svg+xml';
215
+ const url = getFullContentUrl();
216
+ return (
217
+ /*#__PURE__*/
218
+ // eslint-disable-next-line jsx-a11y/alt-text
219
+ _jsx("object", {
220
+ type: objectType,
221
+ data: url,
222
+ onErrorCapture: onError,
223
+ onLoad: onLoad,
224
+ style: {
225
+ width: 'auto',
226
+ height: 'auto',
227
+ pointerEvents: 'none'
228
+ }
229
+ }, url)
230
+ );
231
+ };
123
232
  const renderNFT = () => {
124
233
  if (content) {
125
234
  switch (type) {
126
235
  case 'url':
127
236
  {
128
- const urlObj = new URL(content);
129
- if (!urlObj.searchParams.has('assetId')) {
130
- urlObj.searchParams.append('assetId', address);
237
+ if (state.loadingUrlType) {
238
+ return null;
131
239
  }
132
- if (!urlObj.searchParams.has('vcId') && vcId) {
133
- urlObj.searchParams.append('vcId', vcId);
240
+
241
+ // render image
242
+ if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
243
+ return renderImg();
134
244
  }
135
- const url = urlObj.href;
136
- const safeOnLoad = () => {
137
- if (state.loading) {
138
- setState({
139
- ...state,
140
- loading: false
141
- });
142
- }
143
- };
144
- return state.fallback ? state.fallback?.() : /*#__PURE__*/_jsx(ReactSVG, {
145
- src: url,
146
- beforeInjection: svg => {
147
- svg.setAttribute('style', 'pointer-events: none; width: 100%; height: 100%;');
148
- },
149
- afterInjection: safeOnLoad,
150
- onError: error => {
151
- let objectType = null;
152
- if (error?.message?.indexOf('Invalid content type: ') > -1) {
153
- objectType = error.message.split('Invalid content type: ')?.[1];
154
- } else if (error?.message?.indexOf('Unable to load SVG file: ') > -1) {
155
- objectType = 'image/svg+xml';
156
- }
157
- setState({
158
- ...state,
159
- // fallback to object, and use objectType to render
160
- fallback: () =>
161
- /*#__PURE__*/
162
- // eslint-disable-next-line jsx-a11y/alt-text
163
- _jsx("object", {
164
- type: objectType,
165
- data: url,
166
- onErrorCapture: () => {
167
- setState({
168
- ...state,
169
- error: true,
170
- loading: false
171
- });
172
- },
173
- onLoad: safeOnLoad,
174
- style: {
175
- width: 'auto',
176
- height: 'auto',
177
- pointerEvents: 'none'
178
- }
179
- }, url)
180
- });
181
- }
182
- // evalScripts="always"
183
- ,
184
- wrapper: "div"
185
- });
245
+ return renderObject();
186
246
  }
187
247
  case 'uri':
188
248
  {
@@ -259,7 +319,10 @@ NFTDisplay.propTypes = {
259
319
  // loading 最小显示时间 (避免闪烁)
260
320
  minimumLoadingTime: PropTypes.number,
261
321
  // 完成回调, 无论加载成功|失败
262
- onCompleted: PropTypes.func
322
+ onCompleted: PropTypes.func,
323
+ // 图片处理,参考:https://team.arcblock.io/comment/docs/c158aee4-accd-42f4-9ced-6a23f28c00e0/en/blocklet-image-service-guide
324
+ // 配置参数会全部转发给 Image Service
325
+ imageFilter: PropTypes.object
263
326
  };
264
327
  NFTDisplay.defaultProps = {
265
328
  component: 'span',
@@ -271,7 +334,8 @@ NFTDisplay.defaultProps = {
271
334
  preferredSvgEmbedder: 'img',
272
335
  checkSvg: false,
273
336
  minimumLoadingTime: 0,
274
- onCompleted: noop
337
+ onCompleted: noop,
338
+ imageFilter: null
275
339
  };
276
340
  const Root = styled('div')`
277
341
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.10.13",
3
+ "version": "2.10.15",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -54,12 +54,12 @@
54
54
  "react": ">=18.2.0",
55
55
  "react-router-dom": ">=6.22.3"
56
56
  },
57
- "gitHead": "572b07b7e8fff82c6c16a9f7814867a5f6d17c03",
57
+ "gitHead": "b1c72a6e908b2e4621a64ac5cf3b565d6712c71b",
58
58
  "dependencies": {
59
59
  "@arcblock/did-motif": "^1.1.13",
60
- "@arcblock/icons": "^2.10.13",
61
- "@arcblock/nft-display": "^2.10.13",
62
- "@arcblock/react-hooks": "^2.10.13",
60
+ "@arcblock/icons": "^2.10.15",
61
+ "@arcblock/nft-display": "^2.10.15",
62
+ "@arcblock/react-hooks": "^2.10.15",
63
63
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
64
64
  "@fontsource/inter": "^5.0.16",
65
65
  "@fontsource/ubuntu-mono": "^5.0.18",
@@ -7,7 +7,6 @@ import get from 'lodash/get';
7
7
  import pako from 'pako';
8
8
  import base64 from 'base64-url';
9
9
  import isSvg from 'is-svg';
10
- import { ReactSVG } from 'react-svg';
11
10
  import noop from 'lodash/noop';
12
11
 
13
12
  import AspectRatioContainer from './aspect-ratio-container';
@@ -64,6 +63,7 @@ function NFTDisplay({
64
63
  checkSvg,
65
64
  minimumLoadingTime,
66
65
  onCompleted,
66
+ imageFilter,
67
67
  ...rest
68
68
  }) {
69
69
  const wrapRoot = (children) => (
@@ -90,15 +90,65 @@ function NFTDisplay({
90
90
  const isUrlType = type === 'url';
91
91
 
92
92
  // 首次加载, 对于 url type 的情况, loading 为 true
93
- const [state, setState] = useState({ loading: isUrlType, error: false });
93
+ const [state, setState] = useState({
94
+ loading: isUrlType,
95
+ error: false,
96
+ loadingUrlType: true,
97
+ urlType: null,
98
+ });
94
99
  const [minimumLoadingReady, setMinimumLoadingReady] = useState(minimumLoadingTime <= 0);
95
100
  // console.log('[debug] render', {type, minimumLoadingTime}, JSON.stringify(state))
96
101
 
102
+ // assemble the complete url
103
+ const getFullContentUrl = ({ useImageFilter = false, t } = {}) => {
104
+ const urlObj = new URL(content);
105
+ if (!urlObj.searchParams.has('assetId')) {
106
+ urlObj.searchParams.append('assetId', address);
107
+ }
108
+
109
+ if (!urlObj.searchParams.has('vcId') && vcId) {
110
+ urlObj.searchParams.append('vcId', vcId);
111
+ }
112
+
113
+ // image filter
114
+ if (useImageFilter && imageFilter) {
115
+ Object.entries(imageFilter).forEach(([key, value]) => {
116
+ urlObj.searchParams.append(key, value);
117
+ });
118
+ }
119
+
120
+ // other params
121
+ if (t) {
122
+ urlObj.searchParams.append('t', t);
123
+ }
124
+
125
+ return urlObj.href;
126
+ };
127
+
128
+ // get url content type
129
+ const getUrlContentType = async () => {
130
+ try {
131
+ const response = await fetch(getFullContentUrl({ useImageFilter: false, t: 'nftdisplay' }), {
132
+ method: 'HEAD',
133
+ });
134
+ const contentType = response.headers.get('Content-Type');
135
+ setState({ ...state, loadingUrlType: false, urlType: contentType });
136
+ } catch (error) {
137
+ console.error('Failed to fetch url content type', error);
138
+ setState({ ...state, loadingUrlType: false, urlType: null });
139
+ }
140
+ };
141
+
97
142
  useEffect(() => {
98
143
  let timer;
99
144
  if (minimumLoadingTime > 0) {
100
145
  timer = setTimeout(() => setMinimumLoadingReady(true), minimumLoadingTime);
101
146
  }
147
+
148
+ if (type === 'url') {
149
+ getUrlContentType();
150
+ }
151
+
102
152
  return () => clearTimeout(timer);
103
153
  // eslint-disable-next-line react-hooks/exhaustive-deps
104
154
  }, []);
@@ -115,66 +165,55 @@ function NFTDisplay({
115
165
  throw new Error('Failed to render NFT Display.');
116
166
  }
117
167
 
168
+ const onLoad = () => {
169
+ if (state.loading) {
170
+ setState({ ...state, loading: false });
171
+ }
172
+ };
173
+
174
+ const onError = () => {
175
+ setState({ ...state, error: true, loading: false });
176
+ };
177
+
178
+ // render image
179
+ const renderImg = () => {
180
+ const url = getFullContentUrl({ useImageFilter: true });
181
+ return (
182
+ <img src={url} onError={onError} onLoad={onLoad} alt="NFT Display" style={{ width: 'auto', height: 'auto' }} />
183
+ );
184
+ };
185
+
186
+ // render object
187
+ const renderObject = () => {
188
+ const objectType = state.urlType || 'image/svg+xml';
189
+ const url = getFullContentUrl();
190
+ return (
191
+ // eslint-disable-next-line jsx-a11y/alt-text
192
+ <object
193
+ key={url}
194
+ type={objectType}
195
+ data={url}
196
+ onErrorCapture={onError}
197
+ onLoad={onLoad}
198
+ style={{ width: 'auto', height: 'auto', pointerEvents: 'none' }}
199
+ />
200
+ );
201
+ };
202
+
118
203
  const renderNFT = () => {
119
204
  if (content) {
120
205
  switch (type) {
121
206
  case 'url': {
122
- const urlObj = new URL(content);
123
- if (!urlObj.searchParams.has('assetId')) {
124
- urlObj.searchParams.append('assetId', address);
207
+ if (state.loadingUrlType) {
208
+ return null;
125
209
  }
126
210
 
127
- if (!urlObj.searchParams.has('vcId') && vcId) {
128
- urlObj.searchParams.append('vcId', vcId);
211
+ // render image
212
+ if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
213
+ return renderImg();
129
214
  }
130
215
 
131
- const url = urlObj.href;
132
-
133
- const safeOnLoad = () => {
134
- if (state.loading) {
135
- setState({ ...state, loading: false });
136
- }
137
- };
138
-
139
- return state.fallback ? (
140
- state.fallback?.()
141
- ) : (
142
- <ReactSVG
143
- src={url}
144
- beforeInjection={(svg) => {
145
- svg.setAttribute('style', 'pointer-events: none; width: 100%; height: 100%;');
146
- }}
147
- afterInjection={safeOnLoad}
148
- onError={(error) => {
149
- let objectType = null;
150
- if (error?.message?.indexOf('Invalid content type: ') > -1) {
151
- objectType = error.message.split('Invalid content type: ')?.[1];
152
- } else if (error?.message?.indexOf('Unable to load SVG file: ') > -1) {
153
- objectType = 'image/svg+xml';
154
- }
155
-
156
- setState({
157
- ...state,
158
- // fallback to object, and use objectType to render
159
- fallback: () => (
160
- // eslint-disable-next-line jsx-a11y/alt-text
161
- <object
162
- key={url}
163
- type={objectType}
164
- data={url}
165
- onErrorCapture={() => {
166
- setState({ ...state, error: true, loading: false });
167
- }}
168
- onLoad={safeOnLoad}
169
- style={{ width: 'auto', height: 'auto', pointerEvents: 'none' }}
170
- />
171
- ),
172
- });
173
- }}
174
- // evalScripts="always"
175
- wrapper="div"
176
- />
177
- );
216
+ return renderObject();
178
217
  }
179
218
  case 'uri': {
180
219
  return (
@@ -245,6 +284,9 @@ NFTDisplay.propTypes = {
245
284
  minimumLoadingTime: PropTypes.number,
246
285
  // 完成回调, 无论加载成功|失败
247
286
  onCompleted: PropTypes.func,
287
+ // 图片处理,参考:https://team.arcblock.io/comment/docs/c158aee4-accd-42f4-9ced-6a23f28c00e0/en/blocklet-image-service-guide
288
+ // 配置参数会全部转发给 Image Service
289
+ imageFilter: PropTypes.object,
248
290
  };
249
291
 
250
292
  NFTDisplay.defaultProps = {
@@ -258,6 +300,7 @@ NFTDisplay.defaultProps = {
258
300
  checkSvg: false,
259
301
  minimumLoadingTime: 0,
260
302
  onCompleted: noop,
303
+ imageFilter: null,
261
304
  };
262
305
 
263
306
  const Root = styled('div')`