@lukso/transaction-view-core 0.3.1-dev.a8c9315
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/.turbo/turbo-build.log +33 -0
- package/CHANGELOG.md +104 -0
- package/LICENSE +201 -0
- package/README.md +264 -0
- package/dist/index.cjs +4573 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +493 -0
- package/dist/index.d.ts +493 -0
- package/dist/index.js +4534 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/index.cjs +2480 -0
- package/dist/shared/index.cjs.map +1 -0
- package/dist/shared/index.d.cts +887 -0
- package/dist/shared/index.d.ts +887 -0
- package/dist/shared/index.js +2437 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/styles/index.cjs +58 -0
- package/dist/styles/index.cjs.map +1 -0
- package/dist/styles/index.d.cts +9 -0
- package/dist/styles/index.d.ts +9 -0
- package/dist/styles/index.js +31 -0
- package/dist/styles/index.js.map +1 -0
- package/package.json +57 -0
- package/publish.log +0 -0
- package/src/components/asset-card.ts +426 -0
- package/src/components/contract-card.ts +238 -0
- package/src/components/description-view.ts +605 -0
- package/src/components/index.ts +20 -0
- package/src/components/info-panel.ts +437 -0
- package/src/components/permissions-block.ts +138 -0
- package/src/components/transaction-playback.ts +429 -0
- package/src/components/universal-token-view.ts +281 -0
- package/src/declaration.d.ts +4 -0
- package/src/examples/INTEGRATION_GUIDE.md +240 -0
- package/src/examples/TransactionContentWithReplacement.vue +269 -0
- package/src/index.ts +38 -0
- package/src/registry/index.ts +31 -0
- package/src/registry/lit-view-loader.ts +191 -0
- package/src/shared/address-display.ts +370 -0
- package/src/shared/address-view.ts +385 -0
- package/src/shared/custom-select.ts +164 -0
- package/src/shared/function-name-view.ts +89 -0
- package/src/shared/gas-provider-view.ts +218 -0
- package/src/shared/icon-view.ts +288 -0
- package/src/shared/image-list.ts +261 -0
- package/src/shared/image-loader-controller.ts +160 -0
- package/src/shared/index.ts +34 -0
- package/src/shared/load-web-components.ts +56 -0
- package/src/shared/notification-view.ts +132 -0
- package/src/shared/progress-view.ts +86 -0
- package/src/shared/safe-custom-element.ts +35 -0
- package/src/shared/scroll-to-bottom-button.ts +55 -0
- package/src/shared/signal-controller.ts +124 -0
- package/src/shared/transaction-view-controller.ts +345 -0
- package/src/shared/url-view.ts +129 -0
- package/src/shared/value-view.ts +510 -0
- package/src/styles/index.ts +28 -0
- package/src/styles/styles.css +1 -0
- package/src/vue-entry.ts +15 -0
- package/tailwind.config.cjs +9 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +90 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
import {
|
|
3
|
+
createTransactionPlayback,
|
|
4
|
+
type PlaybackIndexResult,
|
|
5
|
+
type TransactionPlaybackState,
|
|
6
|
+
type ViewContext,
|
|
7
|
+
type ViewVariant,
|
|
8
|
+
} from '@lukso/transaction-view-headless'
|
|
9
|
+
import { css, html, LitElement, type PropertyValues } from 'lit'
|
|
10
|
+
import { property, state } from 'lit/decorators.js'
|
|
11
|
+
import { createRef, type Ref, ref } from 'lit/directives/ref.js'
|
|
12
|
+
import { when } from 'lit/directives/when.js'
|
|
13
|
+
import { getGlobalLitLoader } from '../registry/lit-view-loader'
|
|
14
|
+
import { safeCustomElement } from '../shared/safe-custom-element'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Lit Transaction Playback Component
|
|
18
|
+
*
|
|
19
|
+
* Provides index-based rendering of transactions with support for batch navigation
|
|
20
|
+
* and context-aware view matching.
|
|
21
|
+
*
|
|
22
|
+
* **Single Transaction:**
|
|
23
|
+
* - index="0" → renders the transaction
|
|
24
|
+
* - index="1" → renders the transaction (same)
|
|
25
|
+
* - index="2" → null/error
|
|
26
|
+
*
|
|
27
|
+
* **Batch Transaction:**
|
|
28
|
+
* - index="0" → renders batch summary/expandable view
|
|
29
|
+
* - index="1" → renders first child transaction
|
|
30
|
+
* - index="2" → renders second child transaction
|
|
31
|
+
* - etc.
|
|
32
|
+
*
|
|
33
|
+
* **Context & Variant:**
|
|
34
|
+
* - context: WHERE the transaction is displayed (approval, feed, list, detail, etc.)
|
|
35
|
+
* - variant: HOW the transaction should be rendered (full, collapsed, minimal, etc.)
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```html
|
|
39
|
+
* <!-- Activity feed: compact view -->
|
|
40
|
+
* <transaction-playback
|
|
41
|
+
* .transaction="${transaction}"
|
|
42
|
+
* index="1"
|
|
43
|
+
* context="feed"
|
|
44
|
+
* variant="compact"
|
|
45
|
+
* ></transaction-playback>
|
|
46
|
+
*
|
|
47
|
+
* <!-- Approval flow: full detailed view -->
|
|
48
|
+
* <transaction-playback
|
|
49
|
+
* .transaction="${transaction}"
|
|
50
|
+
* index="1"
|
|
51
|
+
* context="approval"
|
|
52
|
+
* variant="full"
|
|
53
|
+
* ></transaction-playback>
|
|
54
|
+
*
|
|
55
|
+
* <!-- Batch summary: collapsed view -->
|
|
56
|
+
* <transaction-playback
|
|
57
|
+
* .transaction="${transaction}"
|
|
58
|
+
* index="0"
|
|
59
|
+
* context="batch-summary"
|
|
60
|
+
* variant="collapsed"
|
|
61
|
+
* ></transaction-playback>
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
@safeCustomElement('transaction-playback')
|
|
65
|
+
export class TransactionPlayback extends LitElement {
|
|
66
|
+
/** The root transaction to display */
|
|
67
|
+
@property({ type: Object })
|
|
68
|
+
transaction?: DecoderResult
|
|
69
|
+
|
|
70
|
+
/** Index to display (0, 1, 2, ...) */
|
|
71
|
+
@property({ type: Number })
|
|
72
|
+
index = 1 // Default to index 1 (main transaction, skips batch summary)
|
|
73
|
+
|
|
74
|
+
/** Fallback view ID if no match found */
|
|
75
|
+
@property({ type: String })
|
|
76
|
+
fallbackViewId = 'universal-execute'
|
|
77
|
+
|
|
78
|
+
/** Context: WHERE the transaction is displayed */
|
|
79
|
+
@property({ type: String })
|
|
80
|
+
context?: ViewContext
|
|
81
|
+
|
|
82
|
+
/** Variant: HOW the transaction should be rendered */
|
|
83
|
+
@property({ type: String })
|
|
84
|
+
variant?: ViewVariant
|
|
85
|
+
|
|
86
|
+
/** Show debug information */
|
|
87
|
+
@property({ type: Boolean })
|
|
88
|
+
debug = false
|
|
89
|
+
|
|
90
|
+
@state()
|
|
91
|
+
private playbackState?: TransactionPlaybackState
|
|
92
|
+
|
|
93
|
+
@state()
|
|
94
|
+
private currentResult?: PlaybackIndexResult
|
|
95
|
+
|
|
96
|
+
@state()
|
|
97
|
+
private isLoading = false
|
|
98
|
+
|
|
99
|
+
@state()
|
|
100
|
+
private error?: string
|
|
101
|
+
|
|
102
|
+
@state()
|
|
103
|
+
private loadedComponent?: HTMLElement
|
|
104
|
+
|
|
105
|
+
private componentContainerRef: Ref<HTMLDivElement> = createRef()
|
|
106
|
+
private loader = getGlobalLitLoader()
|
|
107
|
+
|
|
108
|
+
static styles = css`
|
|
109
|
+
:host {
|
|
110
|
+
display: block;
|
|
111
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.playback-container {
|
|
115
|
+
position: relative;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.loading-state {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: column;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
padding: 60px 20px;
|
|
124
|
+
color: #6b7280;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.loading-spinner {
|
|
128
|
+
font-size: 32px;
|
|
129
|
+
margin-bottom: 16px;
|
|
130
|
+
animation: spin 2s linear infinite;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@keyframes spin {
|
|
134
|
+
from {
|
|
135
|
+
transform: rotate(0deg);
|
|
136
|
+
}
|
|
137
|
+
to {
|
|
138
|
+
transform: rotate(360deg);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.loading-text {
|
|
143
|
+
font-size: 16px;
|
|
144
|
+
font-weight: 500;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.error-state {
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
padding: 60px 20px;
|
|
153
|
+
color: #dc2626;
|
|
154
|
+
background: #fef2f2;
|
|
155
|
+
border: 1px solid #fecaca;
|
|
156
|
+
border-radius: 12px;
|
|
157
|
+
margin: 20px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.error-icon {
|
|
161
|
+
font-size: 48px;
|
|
162
|
+
margin-bottom: 16px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.error-title {
|
|
166
|
+
font-size: 18px;
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
margin-bottom: 8px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.error-message {
|
|
172
|
+
font-size: 14px;
|
|
173
|
+
text-align: center;
|
|
174
|
+
line-height: 1.4;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.debug-panel {
|
|
178
|
+
margin-top: 16px;
|
|
179
|
+
padding: 12px;
|
|
180
|
+
background: #f9fafb;
|
|
181
|
+
border: 1px solid #e5e7eb;
|
|
182
|
+
border-radius: 8px;
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.debug-title {
|
|
188
|
+
font-weight: 600;
|
|
189
|
+
margin-bottom: 8px;
|
|
190
|
+
color: #374151;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.debug-item {
|
|
194
|
+
display: flex;
|
|
195
|
+
gap: 8px;
|
|
196
|
+
margin-bottom: 4px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.debug-label {
|
|
200
|
+
font-weight: 500;
|
|
201
|
+
color: #6b7280;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.debug-value {
|
|
205
|
+
color: #111827;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.debug-valid {
|
|
209
|
+
color: #059669;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.debug-invalid {
|
|
213
|
+
color: #dc2626;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Component container for dynamically loaded content */
|
|
217
|
+
.component-container {
|
|
218
|
+
display: contents;
|
|
219
|
+
}
|
|
220
|
+
`
|
|
221
|
+
|
|
222
|
+
protected willUpdate(changedProperties: PropertyValues): void {
|
|
223
|
+
// Reload when transaction, index, context, or variant changes
|
|
224
|
+
if (
|
|
225
|
+
changedProperties.has('transaction') ||
|
|
226
|
+
changedProperties.has('index') ||
|
|
227
|
+
changedProperties.has('context') ||
|
|
228
|
+
changedProperties.has('variant')
|
|
229
|
+
) {
|
|
230
|
+
this.loadView()
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
protected updated(changedProperties: PropertyValues): void {
|
|
235
|
+
// Mount component after render
|
|
236
|
+
if (this.loadedComponent && this.componentContainerRef.value) {
|
|
237
|
+
// Clear existing content
|
|
238
|
+
this.componentContainerRef.value.innerHTML = ''
|
|
239
|
+
// Append the new component
|
|
240
|
+
this.componentContainerRef.value.appendChild(this.loadedComponent)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private async loadView() {
|
|
245
|
+
if (!this.transaction) {
|
|
246
|
+
this.error = 'No transaction provided'
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this.isLoading = true
|
|
251
|
+
this.error = undefined
|
|
252
|
+
this.loadedComponent = undefined
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// Create playback state
|
|
256
|
+
this.playbackState = createTransactionPlayback(this.transaction, {
|
|
257
|
+
framework: 'lit',
|
|
258
|
+
fallbackViewId: this.fallbackViewId,
|
|
259
|
+
context: this.context,
|
|
260
|
+
variant: this.variant,
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// Get view at current index
|
|
264
|
+
this.currentResult = this.playbackState.getViewAtIndex(this.index)
|
|
265
|
+
|
|
266
|
+
// Validate index
|
|
267
|
+
if (!this.currentResult.indexInfo.isValid) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Invalid index ${this.index}. Valid indices: ${this.playbackState.batchInfo.availableIndices.join(', ')}`
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Check if we have a match
|
|
274
|
+
if (!this.currentResult.match) {
|
|
275
|
+
throw new Error(`No view found for transaction at index ${this.index}`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Load the component
|
|
279
|
+
const componentClass = await this.loader.loadFromMatch(
|
|
280
|
+
this.currentResult.match
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
// Create component instance with transaction data
|
|
284
|
+
this.loadedComponent = this.loader.createInstance(componentClass, {
|
|
285
|
+
transaction: this.currentResult.transaction,
|
|
286
|
+
address: (this.currentResult.transaction as any).address,
|
|
287
|
+
value: (this.currentResult.transaction as any).value,
|
|
288
|
+
enhancedInfo: (this.currentResult.transaction as any).enhancedInfo,
|
|
289
|
+
title: this.generateTitle(this.currentResult),
|
|
290
|
+
standard: (this.currentResult.transaction as any).standard,
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
this.isLoading = false
|
|
294
|
+
} catch (err) {
|
|
295
|
+
this.error =
|
|
296
|
+
err instanceof Error ? err.message : 'Failed to load transaction view'
|
|
297
|
+
this.isLoading = false
|
|
298
|
+
console.error('Transaction playback error:', err)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private generateTitle(result: PlaybackIndexResult): string {
|
|
303
|
+
if (result.indexInfo.isBatchSummary) {
|
|
304
|
+
const childCount = result.batchInfo.childCount
|
|
305
|
+
return `Batch Operation (${childCount} ${childCount === 1 ? 'transaction' : 'transactions'})`
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const transaction = result.transaction
|
|
309
|
+
if (!transaction) return 'Transaction'
|
|
310
|
+
|
|
311
|
+
const resultType = transaction.resultType
|
|
312
|
+
|
|
313
|
+
if (!resultType) return 'Transaction'
|
|
314
|
+
|
|
315
|
+
switch (resultType) {
|
|
316
|
+
case 'setData':
|
|
317
|
+
case 'setDataBatch':
|
|
318
|
+
return 'Set Data'
|
|
319
|
+
case 'execute':
|
|
320
|
+
case 'executeBatch':
|
|
321
|
+
return 'Execute'
|
|
322
|
+
case 'create':
|
|
323
|
+
return 'Contract Deployment'
|
|
324
|
+
default:
|
|
325
|
+
if (resultType.includes('transfer')) {
|
|
326
|
+
return 'Transfer'
|
|
327
|
+
}
|
|
328
|
+
return resultType.charAt(0).toUpperCase() + resultType.slice(1)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private renderDebugPanel() {
|
|
333
|
+
if (!this.debug || !this.playbackState || !this.currentResult) {
|
|
334
|
+
return html``
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return html`
|
|
338
|
+
<div class="debug-panel">
|
|
339
|
+
<div class="debug-title">🐛 Playback Debug Info</div>
|
|
340
|
+
<div class="debug-item">
|
|
341
|
+
<span class="debug-label">Requested Index:</span>
|
|
342
|
+
<span class="debug-value">${this.currentResult.indexInfo.requested}</span>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="debug-item">
|
|
345
|
+
<span class="debug-label">Is Valid:</span>
|
|
346
|
+
<span class="${this.currentResult.indexInfo.isValid ? 'debug-valid' : 'debug-invalid'}">
|
|
347
|
+
${this.currentResult.indexInfo.isValid ? '✓ Yes' : '✗ No'}
|
|
348
|
+
</span>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="debug-item">
|
|
351
|
+
<span class="debug-label">Is Batch:</span>
|
|
352
|
+
<span class="debug-value">
|
|
353
|
+
${this.playbackState.isBatch ? 'Yes' : 'No'}
|
|
354
|
+
</span>
|
|
355
|
+
</div>
|
|
356
|
+
<div class="debug-item">
|
|
357
|
+
<span class="debug-label">Is Batch Summary:</span>
|
|
358
|
+
<span class="debug-value">
|
|
359
|
+
${this.currentResult.indexInfo.isBatchSummary ? 'Yes' : 'No'}
|
|
360
|
+
</span>
|
|
361
|
+
</div>
|
|
362
|
+
${when(
|
|
363
|
+
this.currentResult.indexInfo.childIndex != null,
|
|
364
|
+
() => html`
|
|
365
|
+
<div class="debug-item">
|
|
366
|
+
<span class="debug-label">Child Index:</span>
|
|
367
|
+
<span class="debug-value">${this.currentResult!.indexInfo.childIndex}</span>
|
|
368
|
+
</div>
|
|
369
|
+
`
|
|
370
|
+
)}
|
|
371
|
+
<div class="debug-item">
|
|
372
|
+
<span class="debug-label">Available Indices:</span>
|
|
373
|
+
<span class="debug-value">
|
|
374
|
+
[${this.playbackState!.batchInfo.availableIndices.join(', ')}]
|
|
375
|
+
</span>
|
|
376
|
+
</div>
|
|
377
|
+
${when(
|
|
378
|
+
this.currentResult.match,
|
|
379
|
+
() => html`
|
|
380
|
+
<div class="debug-item">
|
|
381
|
+
<span class="debug-label">Matched View:</span>
|
|
382
|
+
<span class="debug-value">${this.currentResult!.match!.view.id}</span>
|
|
383
|
+
</div>
|
|
384
|
+
`
|
|
385
|
+
)}
|
|
386
|
+
</div>
|
|
387
|
+
`
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
render() {
|
|
391
|
+
// Loading state
|
|
392
|
+
if (this.isLoading) {
|
|
393
|
+
return html`
|
|
394
|
+
<div class="loading-state">
|
|
395
|
+
<div class="loading-spinner">⏳</div>
|
|
396
|
+
<div class="loading-text">Loading transaction view...</div>
|
|
397
|
+
</div>
|
|
398
|
+
`
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Error state
|
|
402
|
+
if (this.error) {
|
|
403
|
+
return html`
|
|
404
|
+
<div class="error-state">
|
|
405
|
+
<div class="error-icon">❌</div>
|
|
406
|
+
<div class="error-title">Error</div>
|
|
407
|
+
<div class="error-message">${this.error}</div>
|
|
408
|
+
</div>
|
|
409
|
+
${this.renderDebugPanel()}
|
|
410
|
+
`
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Render loaded component
|
|
414
|
+
return html`
|
|
415
|
+
<div class="playback-container">
|
|
416
|
+
<div class="component-container" ${ref(this.componentContainerRef)}>
|
|
417
|
+
<!-- Component will be mounted here via updated() lifecycle -->
|
|
418
|
+
</div>
|
|
419
|
+
${this.renderDebugPanel()}
|
|
420
|
+
</div>
|
|
421
|
+
`
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
declare global {
|
|
426
|
+
interface HTMLElementTagNameMap {
|
|
427
|
+
'transaction-playback': TransactionPlayback
|
|
428
|
+
}
|
|
429
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { css, html, LitElement } from 'lit'
|
|
2
|
+
import { property, state } from 'lit/decorators.js'
|
|
3
|
+
import { when } from 'lit/directives/when.js'
|
|
4
|
+
import { safeCustomElement } from '../shared/safe-custom-element'
|
|
5
|
+
import './asset-card.js'
|
|
6
|
+
import './description-view.js'
|
|
7
|
+
import './info-panel.js'
|
|
8
|
+
|
|
9
|
+
// Import EnhancedInfo type
|
|
10
|
+
type EnhancedInfo = {
|
|
11
|
+
address: `0x${string}`
|
|
12
|
+
standard?: string
|
|
13
|
+
tokenId?: `0x${string}`
|
|
14
|
+
name?: string
|
|
15
|
+
__gqltype?: 'Profile' | 'Asset' | 'Token'
|
|
16
|
+
[key: string]: unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UniversalTokenViewData {
|
|
20
|
+
/** Main transaction address key (ADDRESS or ADDRESS-TOKENID) */
|
|
21
|
+
address?: string
|
|
22
|
+
/** Value being transferred */
|
|
23
|
+
value?: bigint | string
|
|
24
|
+
/** Raw transaction object (DecoderResult) */
|
|
25
|
+
transaction?: any
|
|
26
|
+
/** Enhanced info from resolver */
|
|
27
|
+
enhancedInfo?: EnhancedInfo
|
|
28
|
+
/** Transaction type */
|
|
29
|
+
type?: 'transfer' | 'mint' | 'burn' | 'approve' | 'execute' | 'deploy'
|
|
30
|
+
/** Title text (e.g., "Transfer Token", "Mint Token") */
|
|
31
|
+
title?: string
|
|
32
|
+
/** Title icon name */
|
|
33
|
+
icon?: string
|
|
34
|
+
/** Standard tag text (e.g., "LSP7-DigitalAsset", "LSP8-IdentifiableDigitalAsset") */
|
|
35
|
+
standard?: string
|
|
36
|
+
/** Loading states */
|
|
37
|
+
loading?: {
|
|
38
|
+
asset?: boolean
|
|
39
|
+
metadata?: boolean
|
|
40
|
+
transaction?: boolean
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Universal token view component that combines all core components
|
|
46
|
+
* Can replace most individual Vue transaction views
|
|
47
|
+
*/
|
|
48
|
+
@safeCustomElement('universal-token-view')
|
|
49
|
+
export class UniversalTokenView extends LitElement {
|
|
50
|
+
@property({ type: Object })
|
|
51
|
+
data: UniversalTokenViewData = {}
|
|
52
|
+
|
|
53
|
+
@state()
|
|
54
|
+
private _infoPanelOpen = false
|
|
55
|
+
|
|
56
|
+
static styles = css`
|
|
57
|
+
:host {
|
|
58
|
+
display: block;
|
|
59
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Match the original LSP7Transfer.vue structure exactly */
|
|
63
|
+
.universal-view {
|
|
64
|
+
padding: 16px; /* matches p-4 in Tailwind */
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Title wrapper - matches original layout */
|
|
68
|
+
.title-wrapper {
|
|
69
|
+
display: flex;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Title section with icon - replicates Title.vue */
|
|
74
|
+
.title-section {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 4px;
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
font-weight: 500;
|
|
80
|
+
color: var(--neutral-20, #374151);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.title-icon {
|
|
84
|
+
width: 16px;
|
|
85
|
+
height: 16px;
|
|
86
|
+
flex-shrink: 0;
|
|
87
|
+
margin-right: 4px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* LSP tag - matches original styling exactly */
|
|
91
|
+
.lsp-tag {
|
|
92
|
+
background: #eedde4;
|
|
93
|
+
border-radius: 8px;
|
|
94
|
+
height: 28px;
|
|
95
|
+
padding: 8px;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
font-weight: 500;
|
|
101
|
+
color: #374151;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Info panel spacing - matches mt-4 */
|
|
105
|
+
.info-panel {
|
|
106
|
+
margin-top: 16px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.loading-state {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
padding: 60px 20px;
|
|
115
|
+
color: #6b7280;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.loading-spinner {
|
|
119
|
+
font-size: 32px;
|
|
120
|
+
margin-bottom: 16px;
|
|
121
|
+
animation: spin 2s linear infinite;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@keyframes spin {
|
|
125
|
+
from { transform: rotate(0deg); }
|
|
126
|
+
to { transform: rotate(360deg); }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.loading-text {
|
|
130
|
+
font-size: 16px;
|
|
131
|
+
font-weight: 500;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.error-state {
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
padding: 60px 20px;
|
|
140
|
+
color: #dc2626;
|
|
141
|
+
background: #fef2f2;
|
|
142
|
+
border: 1px solid #fecaca;
|
|
143
|
+
border-radius: 12px;
|
|
144
|
+
margin: 20px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.error-icon {
|
|
148
|
+
font-size: 48px;
|
|
149
|
+
margin-bottom: 16px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.error-title {
|
|
153
|
+
font-size: 18px;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
margin-bottom: 8px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.error-message {
|
|
159
|
+
font-size: 14px;
|
|
160
|
+
text-align: center;
|
|
161
|
+
line-height: 1.4;
|
|
162
|
+
}
|
|
163
|
+
`
|
|
164
|
+
|
|
165
|
+
private _handleInfoPanelToggle() {
|
|
166
|
+
this._infoPanelOpen = !this._infoPanelOpen
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private _handleInfoPanelClose() {
|
|
170
|
+
this._infoPanelOpen = false
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private _buildAssetCardData() {
|
|
174
|
+
const [contractAddress, tokenId] = (this.data.address || '').split('-')
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
name: this.data.enhancedInfo?.name,
|
|
178
|
+
symbol:
|
|
179
|
+
(this.data.enhancedInfo as any)?.symbol ||
|
|
180
|
+
(this.data.enhancedInfo as any)?.lsp4TokenSymbol,
|
|
181
|
+
icon:
|
|
182
|
+
(this.data.enhancedInfo as any)?.icon ||
|
|
183
|
+
(this.data.enhancedInfo as any)?.images?.[0]?.url,
|
|
184
|
+
address: contractAddress,
|
|
185
|
+
tokenId: tokenId,
|
|
186
|
+
formattedTokenId: tokenId ? `${parseInt(tokenId, 16)}` : undefined,
|
|
187
|
+
amount: this.data.value?.toString(),
|
|
188
|
+
decimals: (this.data.enhancedInfo as any)?.decimals,
|
|
189
|
+
loadingImages: this.data.loading?.asset,
|
|
190
|
+
creator: (this.data.enhancedInfo as any)?.creator,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private _renderLoadingState() {
|
|
195
|
+
return html`
|
|
196
|
+
<div class="loading-state">
|
|
197
|
+
<div class="loading-spinner">⏳</div>
|
|
198
|
+
<div class="loading-text">Loading transaction details...</div>
|
|
199
|
+
</div>
|
|
200
|
+
`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private _renderErrorState(message: string) {
|
|
204
|
+
return html`
|
|
205
|
+
<div class="error-state">
|
|
206
|
+
<div class="error-icon">❌</div>
|
|
207
|
+
<div class="error-title">Error Loading Transaction</div>
|
|
208
|
+
<div class="error-message">${message}</div>
|
|
209
|
+
</div>
|
|
210
|
+
`
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
render() {
|
|
214
|
+
// Handle loading states
|
|
215
|
+
const isLoading =
|
|
216
|
+
this.data.loading?.transaction ||
|
|
217
|
+
(this.data.loading?.metadata && !this.data.enhancedInfo) ||
|
|
218
|
+
(this.data.loading?.asset && !this.data.address)
|
|
219
|
+
|
|
220
|
+
if (isLoading) {
|
|
221
|
+
return this._renderLoadingState()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Handle error states
|
|
225
|
+
if (
|
|
226
|
+
!this.data.address &&
|
|
227
|
+
!this.data.enhancedInfo &&
|
|
228
|
+
!this.data.transaction
|
|
229
|
+
) {
|
|
230
|
+
return this._renderErrorState('No transaction data provided')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Replicate the exact structure from LSP7Transfer.vue
|
|
234
|
+
return html`
|
|
235
|
+
<div class="universal-view">
|
|
236
|
+
<!-- Title wrapper with title and LSP tag - exact match -->
|
|
237
|
+
<div class="title-wrapper">
|
|
238
|
+
<div class="title-section">
|
|
239
|
+
${when(
|
|
240
|
+
this.data.icon,
|
|
241
|
+
() => html`
|
|
242
|
+
<svg class="title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
243
|
+
<path d="M7 16l-4-4 4-4M17 8l4 4-4 4" />
|
|
244
|
+
</svg>
|
|
245
|
+
`
|
|
246
|
+
)}
|
|
247
|
+
${this.data.title || 'Transaction'}
|
|
248
|
+
</div>
|
|
249
|
+
${when(
|
|
250
|
+
this.data.standard,
|
|
251
|
+
() => html`<div class="lsp-tag">${this.data.standard}</div>`
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<!-- AssetCard replaces DigitalAssetInfo - exact same functionality -->
|
|
256
|
+
<asset-card
|
|
257
|
+
.data=${this._buildAssetCardData()}
|
|
258
|
+
size="medium"
|
|
259
|
+
?show-info-button=${true}
|
|
260
|
+
@info-click=${this._handleInfoPanelToggle}
|
|
261
|
+
></asset-card>
|
|
262
|
+
|
|
263
|
+
<!-- InfoPanel replaces BackPanelAsset with class="mt-4" spacing -->
|
|
264
|
+
<info-panel
|
|
265
|
+
class="info-panel"
|
|
266
|
+
address=${this.data.address || ''}
|
|
267
|
+
?open=${this._infoPanelOpen}
|
|
268
|
+
standard=${this.data.standard || this.data.enhancedInfo?.standard || ''}
|
|
269
|
+
type=${this.data.type || 'transfer'}
|
|
270
|
+
@close=${this._handleInfoPanelClose}
|
|
271
|
+
></info-panel>
|
|
272
|
+
</div>
|
|
273
|
+
`
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
declare global {
|
|
278
|
+
interface HTMLElementTagNameMap {
|
|
279
|
+
'universal-token-view': UniversalTokenView
|
|
280
|
+
}
|
|
281
|
+
}
|