@lukso/transaction-view-headless 0.2.2-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 +51 -0
- package/CHANGELOG.md +88 -0
- package/COMPONENT_ARCHITECTURE.md +190 -0
- package/COMPONENT_CONVERSION_ANALYSIS.md +371 -0
- package/CONTEXT_VARIANT_GUIDE.md +425 -0
- package/LAZY_LOADING_GUIDE.md +534 -0
- package/LICENSE +201 -0
- package/PLAYBACK_EXAMPLES.md +658 -0
- package/PLAYBACK_GUIDE.md +402 -0
- package/README.md +43 -0
- package/REGISTRY_SUMMARY.md +638 -0
- package/SUFFERING_ECONOMICS.md +346 -0
- package/SUFFERING_ECONOMICS.zip +0 -0
- package/VIEW_REGISTRATION_GUIDE.md +795 -0
- package/dist/composables/index.cjs +1321 -0
- package/dist/composables/index.cjs.map +1 -0
- package/dist/composables/index.d.cts +6 -0
- package/dist/composables/index.d.ts +6 -0
- package/dist/composables/index.js +1276 -0
- package/dist/composables/index.js.map +1 -0
- package/dist/index-CMLU1hKL.d.ts +374 -0
- package/dist/index-CSDjhiKV.d.cts +374 -0
- package/dist/index.cjs +1534 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +1463 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/index.cjs +522 -0
- package/dist/registry/index.cjs.map +1 -0
- package/dist/registry/index.d.cts +46 -0
- package/dist/registry/index.d.ts +46 -0
- package/dist/registry/index.js +487 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/types/index.cjs +19 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +203 -0
- package/dist/types/index.d.ts +203 -0
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +217 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +47 -0
- package/dist/utils/index.d.ts +47 -0
- package/dist/utils/index.js +188 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/view-loader-dD86QAlQ.d.cts +121 -0
- package/dist/view-loader-ii8AxiQR.d.ts +121 -0
- package/package.json +64 -0
- package/publish.log +0 -0
- package/src/composables/index.ts +59 -0
- package/src/composables/use-image-loader.ts +315 -0
- package/src/composables/use-transaction-playback.ts +396 -0
- package/src/composables/use-transaction-view.ts +309 -0
- package/src/config/lukso-config.ts +171 -0
- package/src/examples/context-aware-views.ts +300 -0
- package/src/examples/example-views.ts +197 -0
- package/src/examples/lit-plus-rn-strategy.ts +448 -0
- package/src/examples/lsp7-custom-matcher-examples.ts +174 -0
- package/src/examples/opt-in-context-examples.ts +372 -0
- package/src/examples/optimized-view-definitions.ts +408 -0
- package/src/examples/vue-to-lit-compilation.ts +358 -0
- package/src/hooks/useAddressQuery.ts +135 -0
- package/src/hooks/useAddressResolution.ts +437 -0
- package/src/index.ts +36 -0
- package/src/registry/index.ts +13 -0
- package/src/registry/view-loader.ts +305 -0
- package/src/registry/view-registry.ts +135 -0
- package/src/types/index.ts +16 -0
- package/src/types/view-contexts.ts +72 -0
- package/src/types/view-matching.ts +173 -0
- package/src/utils/context-matcher.ts +165 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/view-matcher.ts +338 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +34 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import type { ImageData } from '@lukso/transaction-decoder'
|
|
2
|
+
|
|
3
|
+
// Force cache invalidation
|
|
4
|
+
console.log(
|
|
5
|
+
'🔥 LOADING NEW useAddressResolver CODE - should see GraphQL integration'
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Image loading configuration for different contexts
|
|
10
|
+
*/
|
|
11
|
+
export interface ImageLoadOptions {
|
|
12
|
+
width?: number
|
|
13
|
+
height?: number
|
|
14
|
+
dpr?: number
|
|
15
|
+
index?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolved image result
|
|
20
|
+
*/
|
|
21
|
+
export interface ImageResult {
|
|
22
|
+
src: string
|
|
23
|
+
width?: number
|
|
24
|
+
height?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Address/Profile data structure for image resolution
|
|
29
|
+
* Note: GraphQL pluralizes field names (icons, not icon)
|
|
30
|
+
*/
|
|
31
|
+
export interface AddressImageData {
|
|
32
|
+
profileImages?: ImageData[]
|
|
33
|
+
avatar?: ImageData[]
|
|
34
|
+
icons?: ImageData[] // GraphQL pluralizes to "icons"
|
|
35
|
+
images?: ImageData[]
|
|
36
|
+
__gqltype?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Image loading cache for preventing duplicate requests
|
|
40
|
+
const imageLoadCache = new Map<string, Promise<ImageResult | null>>()
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate cache key for image loading based on image data and options
|
|
44
|
+
*/
|
|
45
|
+
function getImageCacheKey(imageData: any[], options: ImageLoadOptions): string {
|
|
46
|
+
// Create a stable key based on the image data sources and size requirements
|
|
47
|
+
const imageUrls = imageData
|
|
48
|
+
.filter((img) => img?.url || img?.src)
|
|
49
|
+
.map((img) => img.url || img.src)
|
|
50
|
+
.sort()
|
|
51
|
+
.join(',')
|
|
52
|
+
|
|
53
|
+
const sizeKey = `${options.width || 32}x${options.height || options.width || 32}@${options.dpr || 1}`
|
|
54
|
+
|
|
55
|
+
return `${imageUrls}-${sizeKey}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Composable for loading images from various sources
|
|
60
|
+
* Extracted from Vue AddressView component for framework-agnostic use
|
|
61
|
+
*/
|
|
62
|
+
export function useImageLoader() {
|
|
63
|
+
/**
|
|
64
|
+
* Load image from GraphQL-resolved image groups using proper size selection
|
|
65
|
+
*/
|
|
66
|
+
async function loadImage(
|
|
67
|
+
imageData: any[],
|
|
68
|
+
options: ImageLoadOptions = {}
|
|
69
|
+
): Promise<ImageResult | null> {
|
|
70
|
+
try {
|
|
71
|
+
if (!imageData || imageData.length === 0) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Default options
|
|
76
|
+
const loadOptions = {
|
|
77
|
+
width: options.width || 32,
|
|
78
|
+
height: options.height || options.width || 32,
|
|
79
|
+
dpr:
|
|
80
|
+
options.dpr ||
|
|
81
|
+
(typeof window !== 'undefined' ? window.devicePixelRatio : 1),
|
|
82
|
+
ignoreHead: true, // Skip HEAD requests in component context
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check cache first to prevent duplicate requests
|
|
86
|
+
const cacheKey = getImageCacheKey(imageData, loadOptions)
|
|
87
|
+
const cachedPromise = imageLoadCache.get(cacheKey)
|
|
88
|
+
if (cachedPromise) {
|
|
89
|
+
console.log('📸 loadImage: Using cached promise for', cacheKey)
|
|
90
|
+
return cachedPromise
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(
|
|
94
|
+
'📸 loadImage: Creating new request for',
|
|
95
|
+
imageData.length,
|
|
96
|
+
'options:',
|
|
97
|
+
loadOptions
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Create and cache the loading promise
|
|
101
|
+
const loadingPromise = (async (): Promise<ImageResult | null> => {
|
|
102
|
+
// Import the proper getImage function from decoder package
|
|
103
|
+
const { getImage } = await import('@lukso/transaction-decoder')
|
|
104
|
+
|
|
105
|
+
const result = await getImage(imageData, loadOptions)
|
|
106
|
+
|
|
107
|
+
if (result?.src) {
|
|
108
|
+
console.log('📸 loadImage: Selected image:', result.src)
|
|
109
|
+
return {
|
|
110
|
+
src: result.src,
|
|
111
|
+
width: result.width,
|
|
112
|
+
height: result.height,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return null
|
|
117
|
+
})()
|
|
118
|
+
|
|
119
|
+
// Cache the promise
|
|
120
|
+
imageLoadCache.set(cacheKey, loadingPromise)
|
|
121
|
+
|
|
122
|
+
// Clean up cache on completion (success or failure)
|
|
123
|
+
loadingPromise.finally(() => {
|
|
124
|
+
// Keep successful results cached, remove failures
|
|
125
|
+
loadingPromise
|
|
126
|
+
.then((result) => {
|
|
127
|
+
if (!result) {
|
|
128
|
+
imageLoadCache.delete(cacheKey)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
.catch(() => {
|
|
132
|
+
imageLoadCache.delete(cacheKey)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return loadingPromise
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn('Failed to load image:', error)
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get profile/avatar image from address data
|
|
145
|
+
* Priority differs based on type:
|
|
146
|
+
* - Profiles: profileImages first
|
|
147
|
+
* - NFTs/Contracts: icons first (GraphQL pluralizes to "icons"), then images
|
|
148
|
+
* - Avatars are from other contracts (not used yet)
|
|
149
|
+
*/
|
|
150
|
+
async function loadProfileImage(
|
|
151
|
+
addressData: AddressImageData,
|
|
152
|
+
options: ImageLoadOptions = {}
|
|
153
|
+
): Promise<ImageResult | null> {
|
|
154
|
+
// For Profiles, prioritize profileImages
|
|
155
|
+
if (addressData.__gqltype === 'Profile') {
|
|
156
|
+
// Try profileImages first
|
|
157
|
+
if (addressData.profileImages?.length) {
|
|
158
|
+
const result = await loadImage(addressData.profileImages, options)
|
|
159
|
+
if (result) return result
|
|
160
|
+
}
|
|
161
|
+
// Fallback to icons
|
|
162
|
+
if (addressData.icons?.length) {
|
|
163
|
+
const result = await loadImage(addressData.icons, options)
|
|
164
|
+
if (result) return result
|
|
165
|
+
}
|
|
166
|
+
// Fallback to images
|
|
167
|
+
if (addressData.images?.length) {
|
|
168
|
+
return loadImage(addressData.images, options)
|
|
169
|
+
}
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// For NFTs/Contracts, prioritize icons, fallback to images
|
|
174
|
+
// Tokens/Assets will never have profileImages, so only check icons and images
|
|
175
|
+
// Try icons first
|
|
176
|
+
if (addressData.icons?.length) {
|
|
177
|
+
const result = await loadImage(addressData.icons, options)
|
|
178
|
+
if (result) return result
|
|
179
|
+
}
|
|
180
|
+
// Fallback to images
|
|
181
|
+
if (addressData.images?.length) {
|
|
182
|
+
return loadImage(addressData.images, options)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Load background image from resolved address data
|
|
190
|
+
*/
|
|
191
|
+
async function loadBackgroundImage(
|
|
192
|
+
addressData: AddressImageData & { backgroundImages?: any[] },
|
|
193
|
+
options: ImageLoadOptions = {}
|
|
194
|
+
): Promise<ImageResult | null> {
|
|
195
|
+
const imageGroup = addressData.backgroundImages || []
|
|
196
|
+
return loadImage(imageGroup, options)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get fallback image based on address type
|
|
201
|
+
*/
|
|
202
|
+
function getFallbackImage(addressData: AddressImageData): ImageResult {
|
|
203
|
+
const isProfile = addressData.__gqltype === 'Profile'
|
|
204
|
+
return {
|
|
205
|
+
src: isProfile
|
|
206
|
+
? '/assets/images/profile-default.svg'
|
|
207
|
+
: '/assets/images/token-default.svg',
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Determine if address represents a contract (has square icon)
|
|
213
|
+
*/
|
|
214
|
+
function isContractAddress(addressData: AddressImageData): boolean {
|
|
215
|
+
return addressData.__gqltype !== 'Profile'
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
loadImage,
|
|
220
|
+
loadProfileImage,
|
|
221
|
+
loadBackgroundImage,
|
|
222
|
+
getFallbackImage,
|
|
223
|
+
isContractAddress,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Composable for address resolution and formatting
|
|
229
|
+
* Connected to decoder package's GraphQL AddressResolver
|
|
230
|
+
*/
|
|
231
|
+
export function useAddressResolver() {
|
|
232
|
+
/**
|
|
233
|
+
* Resolve address data using decoder package integration
|
|
234
|
+
* Uses the batched address resolution from useAddressResolution
|
|
235
|
+
*/
|
|
236
|
+
async function resolveAddress(address: string): Promise<any> {
|
|
237
|
+
console.log('🔥 useAddressResolver.resolveAddress called with:', address)
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
// Import and use the batched resolution from the hooks
|
|
241
|
+
const { resolveAddress: batchedResolve } = await import(
|
|
242
|
+
'../hooks/useAddressResolution'
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
console.log('🔥 useAddressResolver: calling batchedResolve...')
|
|
246
|
+
const resolved = await batchedResolve(address)
|
|
247
|
+
console.log('🔥 useAddressResolver: batchedResolve returned:', resolved)
|
|
248
|
+
|
|
249
|
+
// Always return something, even if no profile - this ensures we don't return null unnecessarily
|
|
250
|
+
const result = {
|
|
251
|
+
// Preserve all image arrays from GraphQL
|
|
252
|
+
...resolved,
|
|
253
|
+
// Override with computed values only if not already set by GraphQL
|
|
254
|
+
address: resolved.address,
|
|
255
|
+
name: resolved.name || '',
|
|
256
|
+
__gqltype:
|
|
257
|
+
resolved.__gqltype || (resolved.hasProfile ? 'Profile' : 'Contract'),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log('🔥 useAddressResolver: returning result:', result)
|
|
261
|
+
return result
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('🔥 useAddressResolver: Failed to resolve address:', error)
|
|
264
|
+
|
|
265
|
+
// Return a fallback instead of null
|
|
266
|
+
return {
|
|
267
|
+
address,
|
|
268
|
+
name: '',
|
|
269
|
+
__gqltype: 'Contract',
|
|
270
|
+
profileImages: [],
|
|
271
|
+
avatar: [],
|
|
272
|
+
icon: [],
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Format address display name from resolved data
|
|
279
|
+
*/
|
|
280
|
+
function formatDisplayName(addressData: any): string {
|
|
281
|
+
const baseName =
|
|
282
|
+
addressData.name?.toLowerCase?.() ||
|
|
283
|
+
addressData.lsp4TokenName ||
|
|
284
|
+
addressData.baseAsset?.lsp4TokenName ||
|
|
285
|
+
''
|
|
286
|
+
|
|
287
|
+
const symbol =
|
|
288
|
+
addressData.lsp4TokenSymbol ||
|
|
289
|
+
addressData.baseAsset?.lsp4TokenSymbol ||
|
|
290
|
+
''
|
|
291
|
+
|
|
292
|
+
return symbol ? `${baseName} (${symbol})` : baseName
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get address prefix based on type
|
|
297
|
+
*/
|
|
298
|
+
function getAddressPrefix(addressData: any): string {
|
|
299
|
+
return addressData.__gqltype !== 'Profile' ? '🪙 ' : '@'
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if address should show name colors
|
|
304
|
+
*/
|
|
305
|
+
function shouldShowNameColor(addressData: any): boolean {
|
|
306
|
+
return addressData.__gqltype === 'Profile'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
resolveAddress,
|
|
311
|
+
formatDisplayName,
|
|
312
|
+
getAddressPrefix,
|
|
313
|
+
shouldShowNameColor,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
import type { Chain } from 'viem'
|
|
3
|
+
import { getGlobalRegistry } from '../registry/view-registry'
|
|
4
|
+
import type {
|
|
5
|
+
ViewMatch,
|
|
6
|
+
ViewMatchOptions,
|
|
7
|
+
ViewRegistry,
|
|
8
|
+
} from '../types/view-matching'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Transaction Playback System
|
|
12
|
+
*
|
|
13
|
+
* Provides index-based access to transaction views with smart handling of batches:
|
|
14
|
+
*
|
|
15
|
+
* **Single Transaction:**
|
|
16
|
+
* - Index 0: The transaction itself
|
|
17
|
+
* - Index 1: The transaction itself (same as 0)
|
|
18
|
+
* - Index 2+: null
|
|
19
|
+
*
|
|
20
|
+
* **Batch Transaction (setDataBatch, executeBatch):**
|
|
21
|
+
* - Index 0: Summary/expandable view of the batch
|
|
22
|
+
* - Index 1: First child transaction
|
|
23
|
+
* - Index 2: Second child transaction
|
|
24
|
+
* - Index N: Nth child transaction
|
|
25
|
+
*
|
|
26
|
+
* This design allows UI flexibility:
|
|
27
|
+
* - Always use index 1 to skip batch summaries
|
|
28
|
+
* - Use index 0 to show batch overview (optional)
|
|
29
|
+
* - Navigate batch children by index
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for transaction playback
|
|
34
|
+
*/
|
|
35
|
+
export interface TransactionPlaybackOptions extends Partial<ViewMatchOptions> {
|
|
36
|
+
/** Custom registry to use instead of global */
|
|
37
|
+
registry?: ViewRegistry
|
|
38
|
+
/** Fallback view ID if no matches found */
|
|
39
|
+
fallbackViewId?: string
|
|
40
|
+
/** Chain for address resolution */
|
|
41
|
+
chain?: Chain
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Result of selecting a view at a specific index
|
|
46
|
+
*/
|
|
47
|
+
export interface PlaybackIndexResult {
|
|
48
|
+
/** The selected view match (or null if invalid index) */
|
|
49
|
+
match: ViewMatch | null
|
|
50
|
+
/** The transaction data for this index */
|
|
51
|
+
transaction: DecoderResult | null
|
|
52
|
+
/** Index information */
|
|
53
|
+
indexInfo: {
|
|
54
|
+
/** Requested index */
|
|
55
|
+
requested: number
|
|
56
|
+
/** Actual index in children array (for batches) */
|
|
57
|
+
childIndex: number | null
|
|
58
|
+
/** Whether this is a batch summary view (index 0 of batch) */
|
|
59
|
+
isBatchSummary: boolean
|
|
60
|
+
/** Whether this is a valid index */
|
|
61
|
+
isValid: boolean
|
|
62
|
+
}
|
|
63
|
+
/** Batch information (if applicable) */
|
|
64
|
+
batchInfo: {
|
|
65
|
+
/** Whether the root transaction is a batch */
|
|
66
|
+
isBatch: boolean
|
|
67
|
+
/** Total number of children in batch (0 if not batch) */
|
|
68
|
+
childCount: number
|
|
69
|
+
/** Available indices (e.g., [0, 1, 2, 3] for 3-child batch) */
|
|
70
|
+
availableIndices: number[]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Complete playback state for a transaction
|
|
76
|
+
*/
|
|
77
|
+
export interface TransactionPlaybackState {
|
|
78
|
+
/** The root transaction */
|
|
79
|
+
rootTransaction: DecoderResult
|
|
80
|
+
/** Whether this is a batch transaction */
|
|
81
|
+
isBatch: boolean
|
|
82
|
+
/** Child transactions (empty if not batch) */
|
|
83
|
+
children: DecoderResult[]
|
|
84
|
+
/** Total count of available indices */
|
|
85
|
+
indexCount: number
|
|
86
|
+
/** Batch information */
|
|
87
|
+
batchInfo: {
|
|
88
|
+
/** Whether the root transaction is a batch */
|
|
89
|
+
isBatch: boolean
|
|
90
|
+
/** Total number of children in batch (0 if not batch) */
|
|
91
|
+
childCount: number
|
|
92
|
+
/** Available indices (e.g., [0, 1, 2, 3] for 3-child batch) */
|
|
93
|
+
availableIndices: number[]
|
|
94
|
+
}
|
|
95
|
+
/** Get view match for a specific index */
|
|
96
|
+
getViewAtIndex: (index: number) => PlaybackIndexResult
|
|
97
|
+
/** Get all available views */
|
|
98
|
+
getAllViews: () => PlaybackIndexResult[]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if a transaction is a batch transaction
|
|
103
|
+
*/
|
|
104
|
+
function isBatchTransaction(transaction: DecoderResult): boolean {
|
|
105
|
+
return (
|
|
106
|
+
transaction.resultType === 'executeBatch' ||
|
|
107
|
+
transaction.resultType === 'setDataBatch' ||
|
|
108
|
+
!!(transaction as any).children?.length
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get children from a batch transaction
|
|
114
|
+
*/
|
|
115
|
+
function getBatchChildren(transaction: DecoderResult): DecoderResult[] {
|
|
116
|
+
if (!isBatchTransaction(transaction)) {
|
|
117
|
+
return []
|
|
118
|
+
}
|
|
119
|
+
return (transaction as any).children || []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Core function to select view at a specific index
|
|
124
|
+
*/
|
|
125
|
+
function selectViewAtIndex(
|
|
126
|
+
rootTransaction: DecoderResult,
|
|
127
|
+
index: number,
|
|
128
|
+
options: TransactionPlaybackOptions
|
|
129
|
+
): PlaybackIndexResult {
|
|
130
|
+
const {
|
|
131
|
+
registry = getGlobalRegistry(),
|
|
132
|
+
framework = 'lit',
|
|
133
|
+
fallbackViewId,
|
|
134
|
+
minScore = 0,
|
|
135
|
+
includeFrameworkConfig = true,
|
|
136
|
+
} = options
|
|
137
|
+
|
|
138
|
+
const isBatch = isBatchTransaction(rootTransaction)
|
|
139
|
+
const children = getBatchChildren(rootTransaction)
|
|
140
|
+
const childCount = children.length
|
|
141
|
+
|
|
142
|
+
// Initialize result
|
|
143
|
+
const result: PlaybackIndexResult = {
|
|
144
|
+
match: null,
|
|
145
|
+
transaction: null,
|
|
146
|
+
indexInfo: {
|
|
147
|
+
requested: index,
|
|
148
|
+
childIndex: null,
|
|
149
|
+
isBatchSummary: false,
|
|
150
|
+
isValid: false,
|
|
151
|
+
},
|
|
152
|
+
batchInfo: {
|
|
153
|
+
isBatch,
|
|
154
|
+
childCount,
|
|
155
|
+
availableIndices: [],
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Calculate available indices
|
|
160
|
+
if (isBatch) {
|
|
161
|
+
// Batch: 0 (summary) + N children
|
|
162
|
+
result.batchInfo.availableIndices = Array.from(
|
|
163
|
+
{ length: childCount + 1 },
|
|
164
|
+
(_, i) => i
|
|
165
|
+
)
|
|
166
|
+
} else {
|
|
167
|
+
// Single: 0 and 1 both map to the transaction
|
|
168
|
+
result.batchInfo.availableIndices = [0, 1]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Validate index
|
|
172
|
+
if (index < 0) {
|
|
173
|
+
return result // Invalid: negative index
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle single transaction
|
|
177
|
+
if (!isBatch) {
|
|
178
|
+
if (index === 0 || index === 1) {
|
|
179
|
+
result.indexInfo.isValid = true
|
|
180
|
+
result.transaction = rootTransaction
|
|
181
|
+
|
|
182
|
+
// Find matching view for the transaction
|
|
183
|
+
const matchOptions: ViewMatchOptions = {
|
|
184
|
+
framework: framework as any,
|
|
185
|
+
minScore,
|
|
186
|
+
maxMatches: 1,
|
|
187
|
+
includeFrameworkConfig,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const matches = registry.findMatches(rootTransaction, matchOptions)
|
|
191
|
+
result.match = matches[0] || null
|
|
192
|
+
|
|
193
|
+
// Try fallback if no match
|
|
194
|
+
if (!result.match && fallbackViewId) {
|
|
195
|
+
const fallbackView = registry.getView(fallbackViewId)
|
|
196
|
+
if (fallbackView && fallbackView.frameworks[framework]) {
|
|
197
|
+
result.match = {
|
|
198
|
+
view: fallbackView,
|
|
199
|
+
score: 0,
|
|
200
|
+
matchedCriteria: [],
|
|
201
|
+
frameworkConfig: includeFrameworkConfig
|
|
202
|
+
? fallbackView.frameworks[framework]
|
|
203
|
+
: undefined,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Invalid index for single transaction
|
|
212
|
+
return result
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Handle batch transaction
|
|
216
|
+
if (index === 0) {
|
|
217
|
+
// Index 0: Batch summary view
|
|
218
|
+
result.indexInfo.isValid = true
|
|
219
|
+
result.indexInfo.isBatchSummary = true
|
|
220
|
+
result.transaction = rootTransaction
|
|
221
|
+
|
|
222
|
+
// Find batch summary view
|
|
223
|
+
const matchOptions: ViewMatchOptions = {
|
|
224
|
+
framework: framework as any,
|
|
225
|
+
minScore,
|
|
226
|
+
maxMatches: 1,
|
|
227
|
+
includeFrameworkConfig,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const matches = registry.findMatches(rootTransaction, matchOptions)
|
|
231
|
+
result.match = matches[0] || null
|
|
232
|
+
|
|
233
|
+
// Try fallback if no match
|
|
234
|
+
if (!result.match && fallbackViewId) {
|
|
235
|
+
const fallbackView = registry.getView(fallbackViewId)
|
|
236
|
+
if (fallbackView && fallbackView.frameworks[framework]) {
|
|
237
|
+
result.match = {
|
|
238
|
+
view: fallbackView,
|
|
239
|
+
score: 0,
|
|
240
|
+
matchedCriteria: [],
|
|
241
|
+
frameworkConfig: includeFrameworkConfig
|
|
242
|
+
? fallbackView.frameworks[framework]
|
|
243
|
+
: undefined,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Index 1+: Child transactions
|
|
252
|
+
const childIndex = index - 1
|
|
253
|
+
if (childIndex >= 0 && childIndex < childCount) {
|
|
254
|
+
result.indexInfo.isValid = true
|
|
255
|
+
result.indexInfo.childIndex = childIndex
|
|
256
|
+
result.transaction = children[childIndex]
|
|
257
|
+
|
|
258
|
+
// Find matching view for the child transaction
|
|
259
|
+
const matchOptions: ViewMatchOptions = {
|
|
260
|
+
framework: framework as any,
|
|
261
|
+
minScore,
|
|
262
|
+
maxMatches: 1,
|
|
263
|
+
includeFrameworkConfig,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const matches = registry.findMatches(result.transaction, matchOptions)
|
|
267
|
+
result.match = matches[0] || null
|
|
268
|
+
|
|
269
|
+
// Try fallback if no match
|
|
270
|
+
if (!result.match && fallbackViewId) {
|
|
271
|
+
const fallbackView = registry.getView(fallbackViewId)
|
|
272
|
+
if (fallbackView && fallbackView.frameworks[framework]) {
|
|
273
|
+
result.match = {
|
|
274
|
+
view: fallbackView,
|
|
275
|
+
score: 0,
|
|
276
|
+
matchedCriteria: [],
|
|
277
|
+
frameworkConfig: includeFrameworkConfig
|
|
278
|
+
? fallbackView.frameworks[framework]
|
|
279
|
+
: undefined,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return result
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Invalid index for batch
|
|
288
|
+
return result
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Create a playback state for a transaction
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* const playback = createTransactionPlayback(transaction, { framework: 'lit' })
|
|
297
|
+
*
|
|
298
|
+
* // For single transaction:
|
|
299
|
+
* playback.getViewAtIndex(0) // Returns the transaction
|
|
300
|
+
* playback.getViewAtIndex(1) // Returns the transaction (same)
|
|
301
|
+
* playback.getViewAtIndex(2) // Returns null (invalid)
|
|
302
|
+
*
|
|
303
|
+
* // For batch transaction:
|
|
304
|
+
* playback.getViewAtIndex(0) // Returns batch summary
|
|
305
|
+
* playback.getViewAtIndex(1) // Returns first child
|
|
306
|
+
* playback.getViewAtIndex(2) // Returns second child
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
export function createTransactionPlayback(
|
|
310
|
+
rootTransaction: DecoderResult,
|
|
311
|
+
options: TransactionPlaybackOptions = {}
|
|
312
|
+
): TransactionPlaybackState {
|
|
313
|
+
const isBatch = isBatchTransaction(rootTransaction)
|
|
314
|
+
const children = getBatchChildren(rootTransaction)
|
|
315
|
+
const childCount = children.length
|
|
316
|
+
const indexCount = isBatch ? childCount + 1 : 2 // batch: 0 + N, single: 0 and 1
|
|
317
|
+
|
|
318
|
+
// Calculate available indices
|
|
319
|
+
const availableIndices = isBatch
|
|
320
|
+
? Array.from({ length: childCount + 1 }, (_, i) => i) // [0, 1, 2, ...]
|
|
321
|
+
: [0, 1] // Single: 0 and 1 both valid
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
rootTransaction,
|
|
325
|
+
isBatch,
|
|
326
|
+
children,
|
|
327
|
+
indexCount,
|
|
328
|
+
batchInfo: {
|
|
329
|
+
isBatch,
|
|
330
|
+
childCount,
|
|
331
|
+
availableIndices,
|
|
332
|
+
},
|
|
333
|
+
getViewAtIndex: (index: number) =>
|
|
334
|
+
selectViewAtIndex(rootTransaction, index, options),
|
|
335
|
+
getAllViews: () => {
|
|
336
|
+
const views: PlaybackIndexResult[] = []
|
|
337
|
+
for (let i = 0; i < indexCount; i++) {
|
|
338
|
+
views.push(selectViewAtIndex(rootTransaction, i, options))
|
|
339
|
+
}
|
|
340
|
+
return views
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Simplified composable that just returns the view at a specific index
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* // Vue
|
|
351
|
+
* const index = ref(1) // Always show main transaction, skip batch summaries
|
|
352
|
+
* const result = useTransactionPlayback(transaction, index, { framework: 'lit' })
|
|
353
|
+
*
|
|
354
|
+
* // React
|
|
355
|
+
* const [index, setIndex] = useState(1)
|
|
356
|
+
* const result = useTransactionPlayback(transaction, index, { framework: 'lit' })
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
export function useTransactionPlayback(
|
|
360
|
+
rootTransaction: DecoderResult,
|
|
361
|
+
index: number,
|
|
362
|
+
options: TransactionPlaybackOptions = {}
|
|
363
|
+
): PlaybackIndexResult {
|
|
364
|
+
return selectViewAtIndex(rootTransaction, index, options)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Helper to check if an index is valid for a transaction
|
|
369
|
+
*/
|
|
370
|
+
export function isValidPlaybackIndex(
|
|
371
|
+
transaction: DecoderResult,
|
|
372
|
+
index: number
|
|
373
|
+
): boolean {
|
|
374
|
+
const isBatch = isBatchTransaction(transaction)
|
|
375
|
+
|
|
376
|
+
if (!isBatch) {
|
|
377
|
+
return index === 0 || index === 1
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const childCount = getBatchChildren(transaction).length
|
|
381
|
+
return index >= 0 && index <= childCount
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Helper to get the range of valid indices for a transaction
|
|
386
|
+
*/
|
|
387
|
+
export function getValidPlaybackIndices(transaction: DecoderResult): number[] {
|
|
388
|
+
const isBatch = isBatchTransaction(transaction)
|
|
389
|
+
|
|
390
|
+
if (!isBatch) {
|
|
391
|
+
return [0, 1]
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const childCount = getBatchChildren(transaction).length
|
|
395
|
+
return Array.from({ length: childCount + 1 }, (_, i) => i)
|
|
396
|
+
}
|