@monygroupcorp/micro-web3 1.2.4 → 1.3.1

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.
@@ -1,12 +1,12 @@
1
- import { Component } from '@monygroupcorp/microact';
1
+ import { Component, h } from '@monygroupcorp/microact';
2
2
  import { isIpfsUri, resolveIpfsToHttp, getAvailableGateways } from '../../services/IpfsService.js';
3
3
 
4
4
  /**
5
5
  * IpfsImage Component
6
- *
6
+ *
7
7
  * Renders images with IPFS support, including gateway rotation on failure.
8
8
  * Falls back gracefully if all gateways fail.
9
- *
9
+ *
10
10
  * Props:
11
11
  * - src: Image URL (HTTP or IPFS)
12
12
  * - alt: Alt text for image
@@ -20,41 +20,35 @@ import { isIpfsUri, resolveIpfsToHttp, getAvailableGateways } from '../../servic
20
20
  */
21
21
  export class IpfsImage extends Component {
22
22
  constructor(props = {}) {
23
- super();
24
- this.props = props;
25
-
26
- // State for gateway rotation and loading
27
- this.setState({
23
+ super(props);
24
+
25
+ this.state = {
28
26
  gatewayIndex: 0,
29
27
  isLoading: true,
30
28
  hasError: false,
31
29
  currentSrc: null
32
- });
33
-
34
- // Bind methods
35
- this.handleImageLoad = this.handleImageLoad.bind(this);
36
- this.handleImageError = this.handleImageError.bind(this);
30
+ };
37
31
  }
38
-
32
+
39
33
  /**
40
34
  * Get current HTTP URL based on src and gateway index
41
35
  */
42
36
  getCurrentUrl() {
43
37
  const { src } = this.props;
44
-
38
+
45
39
  if (!src) {
46
40
  return null;
47
41
  }
48
-
42
+
49
43
  // If not IPFS, return as-is
50
44
  if (!isIpfsUri(src)) {
51
45
  return src;
52
46
  }
53
-
47
+
54
48
  // Resolve IPFS to HTTP using current gateway
55
49
  return resolveIpfsToHttp(src, this.state.gatewayIndex);
56
50
  }
57
-
51
+
58
52
  /**
59
53
  * Handle successful image load
60
54
  */
@@ -63,143 +57,126 @@ export class IpfsImage extends Component {
63
57
  isLoading: false,
64
58
  hasError: false
65
59
  });
66
-
60
+
67
61
  // Call user's onLoad callback if provided
68
62
  if (this.props.onLoad) {
69
63
  this.props.onLoad(event);
70
64
  }
71
65
  }
72
-
66
+
73
67
  /**
74
68
  * Handle image load error - try next gateway
75
69
  */
76
70
  handleImageError(event) {
77
71
  const gateways = getAvailableGateways();
78
72
  const { src } = this.props;
79
-
73
+
80
74
  // If not IPFS or out of gateways, show error
81
75
  if (!isIpfsUri(src) || this.state.gatewayIndex >= gateways.length - 1) {
82
76
  this.setState({
83
77
  isLoading: false,
84
78
  hasError: true
85
79
  });
86
-
80
+
87
81
  // Log error for debugging
88
82
  console.error('[IpfsImage] Failed to load image:', src, {
89
83
  triedGateways: this.state.gatewayIndex + 1,
90
84
  totalGateways: gateways.length
91
85
  });
92
-
86
+
93
87
  // Call user's onError callback if provided
94
88
  if (this.props.onError) {
95
89
  this.props.onError(event);
96
90
  }
97
-
91
+
98
92
  return;
99
93
  }
100
-
94
+
101
95
  // Try next gateway
102
96
  const nextIndex = this.state.gatewayIndex + 1;
103
97
  this.setState({
104
98
  gatewayIndex: nextIndex
105
99
  });
106
-
107
- // Update image src to trigger new load attempt
108
- const img = this.element?.querySelector('img');
109
- if (img) {
110
- const nextUrl = resolveIpfsToHttp(src, nextIndex);
111
- if (nextUrl) {
112
- img.src = nextUrl;
113
- }
100
+ }
101
+
102
+ /**
103
+ * Convert style object to CSS style object for h()
104
+ */
105
+ normalizeStyle(style) {
106
+ if (!style || typeof style !== 'object') {
107
+ return {};
114
108
  }
109
+ return style;
110
+ }
111
+
112
+ /**
113
+ * Escape HTML for safe rendering in attributes
114
+ */
115
+ escapeHtml(text) {
116
+ if (!text) return '';
117
+ const div = document.createElement('div');
118
+ div.textContent = text;
119
+ return div.innerHTML;
115
120
  }
116
-
121
+
117
122
  /**
118
123
  * Render component
119
124
  */
120
125
  render() {
121
- const {
122
- src,
123
- alt = '',
124
- className = '',
126
+ const {
127
+ src,
128
+ alt = '',
129
+ className = '',
125
130
  style = {},
126
131
  loading = 'lazy',
127
132
  placeholder,
128
133
  errorPlaceholder
129
134
  } = this.props;
130
-
135
+
131
136
  const { isLoading, hasError } = this.state;
132
-
137
+
133
138
  if (!src) {
134
- return '<div class="ipfs-image-empty"></div>';
139
+ return h('div', { className: 'ipfs-image-empty' });
135
140
  }
136
-
141
+
137
142
  const currentUrl = this.getCurrentUrl();
138
-
143
+
139
144
  // Error state - show placeholder
140
145
  if (hasError) {
141
146
  if (errorPlaceholder) {
142
147
  return errorPlaceholder;
143
148
  }
144
-
149
+
145
150
  // Default error placeholder
146
- return `
147
- <div class="ipfs-image-error ${className}" style="${this.styleToString(style)}">
148
- <div class="ipfs-image-error-icon">⚠️</div>
149
- <div class="ipfs-image-error-text">IPFS image unavailable</div>
150
- </div>
151
- `;
151
+ return h('div', {
152
+ className: `ipfs-image-error ${className}`,
153
+ style: this.normalizeStyle(style)
154
+ },
155
+ h('div', { className: 'ipfs-image-error-icon' }, '\u26A0\uFE0F'),
156
+ h('div', { className: 'ipfs-image-error-text' }, 'IPFS image unavailable')
157
+ );
152
158
  }
153
-
159
+
154
160
  // Loading or loaded state - show image
155
- return `
156
- <div class="ipfs-image-container ${className}" style="${this.styleToString(style)}">
157
- ${isLoading && placeholder ? placeholder : ''}
158
- <img
159
- src="${this.escapeHtml(currentUrl || '')}"
160
- alt="${this.escapeHtml(alt)}"
161
- loading="${loading}"
162
- class="ipfs-image ${isLoading ? 'ipfs-image-loading' : 'ipfs-image-loaded'}"
163
- style="${isLoading ? 'opacity: 0;' : 'opacity: 1; transition: opacity 0.3s;'}"
164
- />
165
- </div>
166
- `;
167
- }
168
-
169
- /**
170
- * Mount component and attach event listeners
171
- */
172
- mount(element) {
173
- super.mount(element);
174
-
175
- // Attach load/error handlers to img element
176
- const img = this.element?.querySelector('img');
177
- if (img) {
178
- img.addEventListener('load', this.handleImageLoad);
179
- img.addEventListener('error', this.handleImageError);
180
-
181
- // Register cleanup
182
- this.registerCleanup(() => {
183
- img.removeEventListener('load', this.handleImageLoad);
184
- img.removeEventListener('error', this.handleImageError);
185
- });
186
- }
187
- }
188
-
189
- /**
190
- * Convert style object to string
191
- */
192
- styleToString(style) {
193
- if (!style || typeof style !== 'object') {
194
- return '';
195
- }
196
-
197
- return Object.entries(style)
198
- .map(([key, value]) => {
199
- const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
200
- return `${cssKey}: ${value};`;
161
+ const imageStyle = isLoading
162
+ ? { opacity: 0 }
163
+ : { opacity: 1, transition: 'opacity 0.3s' };
164
+
165
+ return h('div', {
166
+ className: `ipfs-image-container ${className}`,
167
+ style: this.normalizeStyle(style)
168
+ },
169
+ isLoading && placeholder,
170
+ h('img', {
171
+ src: currentUrl || '',
172
+ alt: alt,
173
+ loading: loading,
174
+ className: `ipfs-image ${isLoading ? 'ipfs-image-loading' : 'ipfs-image-loaded'}`,
175
+ style: imageStyle,
176
+ onLoad: this.bind(this.handleImageLoad),
177
+ onError: this.bind(this.handleImageError)
201
178
  })
202
- .join(' ');
179
+ );
203
180
  }
204
181
  }
205
182
 
@@ -211,23 +188,23 @@ IpfsImage.styles = `
211
188
  width: 100%;
212
189
  height: 100%;
213
190
  }
214
-
191
+
215
192
  .ipfs-image {
216
193
  display: block;
217
194
  width: 100%;
218
195
  height: 100%;
219
196
  object-fit: cover;
220
197
  }
221
-
198
+
222
199
  .ipfs-image-loading {
223
200
  opacity: 0;
224
201
  }
225
-
202
+
226
203
  .ipfs-image-loaded {
227
204
  opacity: 1;
228
205
  transition: opacity 0.3s ease-in;
229
206
  }
230
-
207
+
231
208
  .ipfs-image-error {
232
209
  display: flex;
233
210
  flex-direction: column;
@@ -239,27 +216,26 @@ IpfsImage.styles = `
239
216
  padding: 1rem;
240
217
  text-align: center;
241
218
  }
242
-
219
+
243
220
  .ipfs-image-error-icon {
244
221
  font-size: 2rem;
245
222
  margin-bottom: 0.5rem;
246
223
  }
247
-
224
+
248
225
  .ipfs-image-error-text {
249
226
  font-size: 0.875rem;
250
227
  }
251
-
228
+
252
229
  .ipfs-image-empty {
253
230
  display: block;
254
231
  width: 100%;
255
232
  height: 100%;
256
233
  background-color: transparent;
257
234
  }
258
-
235
+
259
236
  /* Dark mode support */
260
237
  html[data-theme='dark'] .ipfs-image-error {
261
238
  background-color: #2a2a2a;
262
239
  color: #aaa;
263
240
  }
264
241
  `;
265
-