@arcblock/ux 2.10.14 → 2.10.16

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,77 @@ 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
+
113
+ // check protocol
114
+ if (window.location.protocol === 'https:' && urlObj.protocol === 'http:') {
115
+ urlObj.protocol = 'https:';
116
+ }
117
+ if (!urlObj.searchParams.has('assetId')) {
118
+ urlObj.searchParams.append('assetId', address);
119
+ }
120
+ if (!urlObj.searchParams.has('vcId') && vcId) {
121
+ urlObj.searchParams.append('vcId', vcId);
122
+ }
123
+
124
+ // image filter
125
+ if (useImageFilter && imageFilter) {
126
+ Object.entries(imageFilter).forEach(([key, value]) => {
127
+ urlObj.searchParams.append(key, value);
128
+ });
129
+ }
130
+
131
+ // other params
132
+ if (t) {
133
+ urlObj.searchParams.append('t', t);
134
+ }
135
+ return urlObj.href;
136
+ };
137
+
138
+ // get url content type
139
+ const getUrlContentType = async () => {
140
+ try {
141
+ const response = await fetch(getFullContentUrl({
142
+ useImageFilter: false,
143
+ t: 'nftdisplay'
144
+ }), {
145
+ method: 'HEAD'
146
+ });
147
+ const contentType = response.headers.get('Content-Type');
148
+ setState({
149
+ ...state,
150
+ loadingUrlType: false,
151
+ urlType: contentType
152
+ });
153
+ } catch (error) {
154
+ console.error('Failed to fetch url content type', error);
155
+ setState({
156
+ ...state,
157
+ loadingUrlType: false,
158
+ urlType: null
159
+ });
160
+ }
161
+ };
104
162
  useEffect(() => {
105
163
  let timer;
106
164
  if (minimumLoadingTime > 0) {
107
165
  timer = setTimeout(() => setMinimumLoadingReady(true), minimumLoadingTime);
108
166
  }
167
+ if (type === 'url') {
168
+ getUrlContentType();
169
+ }
109
170
  return () => clearTimeout(timer);
110
171
  // eslint-disable-next-line react-hooks/exhaustive-deps
111
172
  }, []);
@@ -120,69 +181,73 @@ function NFTDisplay({
120
181
  if (state.error) {
121
182
  throw new Error('Failed to render NFT Display.');
122
183
  }
184
+ const onLoad = () => {
185
+ if (state.loading) {
186
+ setState({
187
+ ...state,
188
+ loading: false
189
+ });
190
+ }
191
+ };
192
+ const onError = () => {
193
+ setState({
194
+ ...state,
195
+ error: true,
196
+ loading: false
197
+ });
198
+ };
199
+
200
+ // render image
201
+ const renderImg = () => {
202
+ const url = getFullContentUrl({
203
+ useImageFilter: true
204
+ });
205
+ return /*#__PURE__*/_jsx("img", {
206
+ src: url,
207
+ onError: onError,
208
+ onLoad: onLoad,
209
+ alt: "NFT Display",
210
+ style: {
211
+ width: 'auto',
212
+ height: 'auto'
213
+ }
214
+ });
215
+ };
216
+
217
+ // render object
218
+ const renderObject = () => {
219
+ const objectType = state.urlType || 'image/svg+xml';
220
+ const url = getFullContentUrl();
221
+ return (
222
+ /*#__PURE__*/
223
+ // eslint-disable-next-line jsx-a11y/alt-text
224
+ _jsx("object", {
225
+ type: objectType,
226
+ data: url,
227
+ onErrorCapture: onError,
228
+ onLoad: onLoad,
229
+ style: {
230
+ width: 'auto',
231
+ height: 'auto',
232
+ pointerEvents: 'none'
233
+ }
234
+ }, url)
235
+ );
236
+ };
123
237
  const renderNFT = () => {
124
238
  if (content) {
125
239
  switch (type) {
126
240
  case 'url':
127
241
  {
128
- const urlObj = new URL(content);
129
- if (!urlObj.searchParams.has('assetId')) {
130
- urlObj.searchParams.append('assetId', address);
242
+ if (state.loadingUrlType) {
243
+ return null;
131
244
  }
132
- if (!urlObj.searchParams.has('vcId') && vcId) {
133
- urlObj.searchParams.append('vcId', vcId);
245
+
246
+ // render image
247
+ if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
248
+ return renderImg();
134
249
  }
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
- });
250
+ return renderObject();
186
251
  }
187
252
  case 'uri':
188
253
  {
@@ -259,7 +324,10 @@ NFTDisplay.propTypes = {
259
324
  // loading 最小显示时间 (避免闪烁)
260
325
  minimumLoadingTime: PropTypes.number,
261
326
  // 完成回调, 无论加载成功|失败
262
- onCompleted: PropTypes.func
327
+ onCompleted: PropTypes.func,
328
+ // 图片处理,参考:https://team.arcblock.io/comment/docs/c158aee4-accd-42f4-9ced-6a23f28c00e0/en/blocklet-image-service-guide
329
+ // 配置参数会全部转发给 Image Service
330
+ imageFilter: PropTypes.object
263
331
  };
264
332
  NFTDisplay.defaultProps = {
265
333
  component: 'span',
@@ -271,7 +339,8 @@ NFTDisplay.defaultProps = {
271
339
  preferredSvgEmbedder: 'img',
272
340
  checkSvg: false,
273
341
  minimumLoadingTime: 0,
274
- onCompleted: noop
342
+ onCompleted: noop,
343
+ imageFilter: null
275
344
  };
276
345
  const Root = styled('div')`
277
346
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.10.14",
3
+ "version": "2.10.16",
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": "11320e1a4438e518799515f9e30f5fa091f6cf60",
57
+ "gitHead": "4e446d45fbf0db374ffe0ebc9ba0b9dce08fbe0a",
58
58
  "dependencies": {
59
59
  "@arcblock/did-motif": "^1.1.13",
60
- "@arcblock/icons": "^2.10.14",
61
- "@arcblock/nft-display": "^2.10.14",
62
- "@arcblock/react-hooks": "^2.10.14",
60
+ "@arcblock/icons": "^2.10.16",
61
+ "@arcblock/nft-display": "^2.10.16",
62
+ "@arcblock/react-hooks": "^2.10.16",
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,71 @@ 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
+
106
+ // check protocol
107
+ if (window.location.protocol === 'https:' && urlObj.protocol === 'http:') {
108
+ urlObj.protocol = 'https:';
109
+ }
110
+
111
+ if (!urlObj.searchParams.has('assetId')) {
112
+ urlObj.searchParams.append('assetId', address);
113
+ }
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
+
131
+ return urlObj.href;
132
+ };
133
+
134
+ // get url content type
135
+ const getUrlContentType = async () => {
136
+ try {
137
+ const response = await fetch(getFullContentUrl({ useImageFilter: false, t: 'nftdisplay' }), {
138
+ method: 'HEAD',
139
+ });
140
+ const contentType = response.headers.get('Content-Type');
141
+ setState({ ...state, loadingUrlType: false, urlType: contentType });
142
+ } catch (error) {
143
+ console.error('Failed to fetch url content type', error);
144
+ setState({ ...state, loadingUrlType: false, urlType: null });
145
+ }
146
+ };
147
+
97
148
  useEffect(() => {
98
149
  let timer;
99
150
  if (minimumLoadingTime > 0) {
100
151
  timer = setTimeout(() => setMinimumLoadingReady(true), minimumLoadingTime);
101
152
  }
153
+
154
+ if (type === 'url') {
155
+ getUrlContentType();
156
+ }
157
+
102
158
  return () => clearTimeout(timer);
103
159
  // eslint-disable-next-line react-hooks/exhaustive-deps
104
160
  }, []);
@@ -115,66 +171,55 @@ function NFTDisplay({
115
171
  throw new Error('Failed to render NFT Display.');
116
172
  }
117
173
 
174
+ const onLoad = () => {
175
+ if (state.loading) {
176
+ setState({ ...state, loading: false });
177
+ }
178
+ };
179
+
180
+ const onError = () => {
181
+ setState({ ...state, error: true, loading: false });
182
+ };
183
+
184
+ // render image
185
+ const renderImg = () => {
186
+ const url = getFullContentUrl({ useImageFilter: true });
187
+ return (
188
+ <img src={url} onError={onError} onLoad={onLoad} alt="NFT Display" style={{ width: 'auto', height: 'auto' }} />
189
+ );
190
+ };
191
+
192
+ // render object
193
+ const renderObject = () => {
194
+ const objectType = state.urlType || 'image/svg+xml';
195
+ const url = getFullContentUrl();
196
+ return (
197
+ // eslint-disable-next-line jsx-a11y/alt-text
198
+ <object
199
+ key={url}
200
+ type={objectType}
201
+ data={url}
202
+ onErrorCapture={onError}
203
+ onLoad={onLoad}
204
+ style={{ width: 'auto', height: 'auto', pointerEvents: 'none' }}
205
+ />
206
+ );
207
+ };
208
+
118
209
  const renderNFT = () => {
119
210
  if (content) {
120
211
  switch (type) {
121
212
  case 'url': {
122
- const urlObj = new URL(content);
123
- if (!urlObj.searchParams.has('assetId')) {
124
- urlObj.searchParams.append('assetId', address);
213
+ if (state.loadingUrlType) {
214
+ return null;
125
215
  }
126
216
 
127
- if (!urlObj.searchParams.has('vcId') && vcId) {
128
- urlObj.searchParams.append('vcId', vcId);
217
+ // render image
218
+ if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
219
+ return renderImg();
129
220
  }
130
221
 
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
- );
222
+ return renderObject();
178
223
  }
179
224
  case 'uri': {
180
225
  return (
@@ -245,6 +290,9 @@ NFTDisplay.propTypes = {
245
290
  minimumLoadingTime: PropTypes.number,
246
291
  // 完成回调, 无论加载成功|失败
247
292
  onCompleted: PropTypes.func,
293
+ // 图片处理,参考:https://team.arcblock.io/comment/docs/c158aee4-accd-42f4-9ced-6a23f28c00e0/en/blocklet-image-service-guide
294
+ // 配置参数会全部转发给 Image Service
295
+ imageFilter: PropTypes.object,
248
296
  };
249
297
 
250
298
  NFTDisplay.defaultProps = {
@@ -258,6 +306,7 @@ NFTDisplay.defaultProps = {
258
306
  checkSvg: false,
259
307
  minimumLoadingTime: 0,
260
308
  onCompleted: noop,
309
+ imageFilter: null,
261
310
  };
262
311
 
263
312
  const Root = styled('div')`