@arcblock/ux 2.10.13 → 2.10.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -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')`