@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.
package/lib/NFTDisplay/index.js
CHANGED
|
@@ -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
|
-
|
|
129
|
-
|
|
130
|
-
urlObj.searchParams.append('assetId', address);
|
|
242
|
+
if (state.loadingUrlType) {
|
|
243
|
+
return null;
|
|
131
244
|
}
|
|
132
|
-
|
|
133
|
-
|
|
245
|
+
|
|
246
|
+
// render image
|
|
247
|
+
if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
|
|
248
|
+
return renderImg();
|
|
134
249
|
}
|
|
135
|
-
|
|
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.
|
|
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": "
|
|
57
|
+
"gitHead": "4e446d45fbf0db374ffe0ebc9ba0b9dce08fbe0a",
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@arcblock/did-motif": "^1.1.13",
|
|
60
|
-
"@arcblock/icons": "^2.10.
|
|
61
|
-
"@arcblock/nft-display": "^2.10.
|
|
62
|
-
"@arcblock/react-hooks": "^2.10.
|
|
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",
|
package/src/NFTDisplay/index.js
CHANGED
|
@@ -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({
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
urlObj.searchParams.append('assetId', address);
|
|
213
|
+
if (state.loadingUrlType) {
|
|
214
|
+
return null;
|
|
125
215
|
}
|
|
126
216
|
|
|
127
|
-
|
|
128
|
-
|
|
217
|
+
// render image
|
|
218
|
+
if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
|
|
219
|
+
return renderImg();
|
|
129
220
|
}
|
|
130
221
|
|
|
131
|
-
|
|
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')`
|