@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,305 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FrameworkViewConfig,
|
|
3
|
+
ViewLoader,
|
|
4
|
+
ViewMatch,
|
|
5
|
+
} from '../types/view-matching'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when view loading fails
|
|
9
|
+
*/
|
|
10
|
+
export class ViewLoadingError extends Error {
|
|
11
|
+
public readonly loader: ViewLoader
|
|
12
|
+
public readonly config: FrameworkViewConfig
|
|
13
|
+
public override readonly cause?: Error
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
loader: ViewLoader,
|
|
18
|
+
config: FrameworkViewConfig,
|
|
19
|
+
cause?: Error
|
|
20
|
+
) {
|
|
21
|
+
super(message)
|
|
22
|
+
this.name = 'ViewLoadingError'
|
|
23
|
+
this.loader = loader
|
|
24
|
+
this.config = config
|
|
25
|
+
this.cause = cause
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Framework-agnostic view loader that handles different loading strategies
|
|
31
|
+
*/
|
|
32
|
+
export class UniversalViewLoader {
|
|
33
|
+
private cache = new Map<string, any>()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load a component based on the framework configuration
|
|
37
|
+
*/
|
|
38
|
+
async loadComponent(config: FrameworkViewConfig): Promise<any> {
|
|
39
|
+
const cacheKey = this.getCacheKey(config)
|
|
40
|
+
|
|
41
|
+
// Return cached component if available
|
|
42
|
+
if (this.cache.has(cacheKey)) {
|
|
43
|
+
return this.cache.get(cacheKey)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const component = await this.loadByStrategy(config.loader)
|
|
48
|
+
this.cache.set(cacheKey, component)
|
|
49
|
+
return component
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new ViewLoadingError(
|
|
52
|
+
`Failed to load component "${config.component}"`,
|
|
53
|
+
config.loader,
|
|
54
|
+
config,
|
|
55
|
+
error instanceof Error ? error : new Error(String(error))
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load a component from a view match
|
|
62
|
+
*/
|
|
63
|
+
async loadFromMatch(match: ViewMatch): Promise<any> {
|
|
64
|
+
if (!match.frameworkConfig) {
|
|
65
|
+
throw new ViewLoadingError(
|
|
66
|
+
`No framework config available for view "${match.view.id}"`,
|
|
67
|
+
{ type: 'dynamic-import', path: '' },
|
|
68
|
+
{} as FrameworkViewConfig
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this.loadComponent(match.frameworkConfig)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Load component using the specific strategy
|
|
77
|
+
*/
|
|
78
|
+
private async loadByStrategy(loader: ViewLoader): Promise<any> {
|
|
79
|
+
switch (loader.type) {
|
|
80
|
+
case 'dynamic-import':
|
|
81
|
+
return this.loadDynamicImport(loader.path)
|
|
82
|
+
|
|
83
|
+
case 'static-import':
|
|
84
|
+
return this.loadStaticImport(loader.path)
|
|
85
|
+
|
|
86
|
+
case 'custom-element':
|
|
87
|
+
return this.loadCustomElement(loader.tagName)
|
|
88
|
+
|
|
89
|
+
case 'registry-lookup':
|
|
90
|
+
return this.loadFromRegistry(loader.registryKey)
|
|
91
|
+
|
|
92
|
+
case 'custom':
|
|
93
|
+
return loader.loader({} as FrameworkViewConfig) // Will be passed proper config in real usage
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
throw new Error(`Unknown loader type: ${(loader as any).type}`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Dynamic import strategy (most common)
|
|
102
|
+
* Supports both static strings and lazy-loaded callbacks
|
|
103
|
+
*/
|
|
104
|
+
private async loadDynamicImport(
|
|
105
|
+
path: string | (() => Promise<any>)
|
|
106
|
+
): Promise<any> {
|
|
107
|
+
// Handle callback-based lazy loading
|
|
108
|
+
if (typeof path === 'function') {
|
|
109
|
+
const module = await path()
|
|
110
|
+
|
|
111
|
+
// Handle different export patterns
|
|
112
|
+
if (module.default) {
|
|
113
|
+
return module.default
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return module
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Handle string-based path (static)
|
|
120
|
+
const module = await import(path)
|
|
121
|
+
|
|
122
|
+
// Handle different export patterns
|
|
123
|
+
if (module.default) {
|
|
124
|
+
return module.default
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// If no default export, return the entire module
|
|
128
|
+
return module
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Static import strategy (requires bundler support)
|
|
133
|
+
* Supports both static strings and lazy-loaded callbacks
|
|
134
|
+
*/
|
|
135
|
+
private async loadStaticImport(
|
|
136
|
+
path: string | (() => Promise<any>)
|
|
137
|
+
): Promise<any> {
|
|
138
|
+
// Handle callback-based lazy loading
|
|
139
|
+
if (typeof path === 'function') {
|
|
140
|
+
const module = await path()
|
|
141
|
+
|
|
142
|
+
if (module.default) {
|
|
143
|
+
return module.default
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return module
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// This would typically be handled by bundler transformation
|
|
150
|
+
// For now, fall back to dynamic import
|
|
151
|
+
console.warn(
|
|
152
|
+
'Static import not implemented, falling back to dynamic import'
|
|
153
|
+
)
|
|
154
|
+
return this.loadDynamicImport(path)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Custom element strategy (for Lit components)
|
|
159
|
+
*/
|
|
160
|
+
private async loadCustomElement(tagName: string): Promise<any> {
|
|
161
|
+
// Wait for custom element to be defined
|
|
162
|
+
if (typeof window !== 'undefined' && window.customElements) {
|
|
163
|
+
await window.customElements.whenDefined(tagName)
|
|
164
|
+
return { tagName, type: 'custom-element' }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Error('Custom elements not available in this environment')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Registry lookup strategy (for pre-registered components)
|
|
172
|
+
*/
|
|
173
|
+
private async loadFromRegistry(registryKey: string): Promise<any> {
|
|
174
|
+
// This would look up in a component registry
|
|
175
|
+
// Implementation depends on framework
|
|
176
|
+
throw new Error(`Registry lookup not implemented for key: ${registryKey}`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Generate cache key for component config
|
|
181
|
+
*/
|
|
182
|
+
private getCacheKey(config: FrameworkViewConfig): string {
|
|
183
|
+
const { loader, component, package: pkg } = config
|
|
184
|
+
return JSON.stringify({ loader, component, package: pkg })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Clear the component cache
|
|
189
|
+
*/
|
|
190
|
+
clearCache(): void {
|
|
191
|
+
this.cache.clear()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get cache statistics
|
|
196
|
+
*/
|
|
197
|
+
getCacheStats(): { size: number; keys: string[] } {
|
|
198
|
+
return {
|
|
199
|
+
size: this.cache.size,
|
|
200
|
+
keys: Array.from(this.cache.keys()),
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Singleton loader instance
|
|
207
|
+
*/
|
|
208
|
+
let globalLoader: UniversalViewLoader | null = null
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get the global view loader
|
|
212
|
+
*/
|
|
213
|
+
export function getGlobalLoader(): UniversalViewLoader {
|
|
214
|
+
if (!globalLoader) {
|
|
215
|
+
globalLoader = new UniversalViewLoader()
|
|
216
|
+
}
|
|
217
|
+
return globalLoader
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Set a custom global loader (useful for testing)
|
|
222
|
+
*/
|
|
223
|
+
export function setGlobalLoader(loader: UniversalViewLoader): void {
|
|
224
|
+
globalLoader = loader
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Framework-specific helpers for strategically optimized loading patterns
|
|
229
|
+
*
|
|
230
|
+
* Following our Suffering Economics optimization, we support:
|
|
231
|
+
* - Lit Web Components (universal web compatibility) - REQUIRED
|
|
232
|
+
* - Vue (native Vue optimization) - OPTIONAL
|
|
233
|
+
* - React Native (mobile-native optimization) - OPTIONAL (WebView fallback available)
|
|
234
|
+
*
|
|
235
|
+
* All loaders support lazy loading via callbacks:
|
|
236
|
+
* ```typescript
|
|
237
|
+
* // Static (bundled)
|
|
238
|
+
* vueComponent('./Component.vue')
|
|
239
|
+
*
|
|
240
|
+
* // Lazy (loaded on demand)
|
|
241
|
+
* vueComponent(() => import('./Component.vue'))
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
export const LoaderHelpers = {
|
|
245
|
+
/**
|
|
246
|
+
* Create a Lit custom element loader (universal web compatibility)
|
|
247
|
+
*/
|
|
248
|
+
litComponent(tagName: string): ViewLoader {
|
|
249
|
+
return { type: 'custom-element', tagName }
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create a Vue component loader (native Vue, optional)
|
|
254
|
+
* Supports both static paths and lazy-loaded callbacks
|
|
255
|
+
*/
|
|
256
|
+
vueComponent(path: string | (() => Promise<any>)): ViewLoader {
|
|
257
|
+
return { type: 'dynamic-import', path }
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Create a React Native JSX dynamic import loader (mobile-native, optional)
|
|
262
|
+
* Supports both static paths and lazy-loaded callbacks
|
|
263
|
+
*/
|
|
264
|
+
rnComponent(path: string | (() => Promise<any>)): ViewLoader {
|
|
265
|
+
return { type: 'dynamic-import', path }
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Create a custom loader function for advanced use cases
|
|
270
|
+
*/
|
|
271
|
+
custom(loader: (config: FrameworkViewConfig) => Promise<any>): ViewLoader {
|
|
272
|
+
return { type: 'custom', loader }
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// Legacy helpers for compilation pipelines (Vue/React → Lit)
|
|
276
|
+
/**
|
|
277
|
+
* @deprecated Use litComponent() instead. This exists for compilation pipeline compatibility.
|
|
278
|
+
*/
|
|
279
|
+
vueToLitCompiled(tagName: string): ViewLoader {
|
|
280
|
+
console.warn(
|
|
281
|
+
'vueToLitCompiled is deprecated. Use litComponent() - the output is the same.'
|
|
282
|
+
)
|
|
283
|
+
return { type: 'custom-element', tagName }
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @deprecated Use litComponent() instead. This exists for compilation pipeline compatibility.
|
|
288
|
+
*/
|
|
289
|
+
reactToLitCompiled(tagName: string): ViewLoader {
|
|
290
|
+
console.warn(
|
|
291
|
+
'reactToLitCompiled is deprecated. Use litComponent() - the output is the same.'
|
|
292
|
+
)
|
|
293
|
+
return { type: 'custom-element', tagName }
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @deprecated Use rnComponent() instead. Updated for better TypeScript support.
|
|
298
|
+
*/
|
|
299
|
+
reactNativeComponent(path: string): ViewLoader {
|
|
300
|
+
console.warn(
|
|
301
|
+
'reactNativeComponent is deprecated. Use rnComponent() instead.'
|
|
302
|
+
)
|
|
303
|
+
return { type: 'dynamic-import', path }
|
|
304
|
+
},
|
|
305
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
import type {
|
|
3
|
+
ViewDefinition,
|
|
4
|
+
ViewMatch,
|
|
5
|
+
ViewMatchOptions,
|
|
6
|
+
ViewRegistry,
|
|
7
|
+
} from '../types/view-matching'
|
|
8
|
+
import { findMatchingViews } from '../utils/view-matcher'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default view registry implementation
|
|
12
|
+
*/
|
|
13
|
+
export class DefaultViewRegistry implements ViewRegistry {
|
|
14
|
+
private views = new Map<string, ViewDefinition>()
|
|
15
|
+
|
|
16
|
+
register(view: ViewDefinition): void {
|
|
17
|
+
if (this.views.has(view.id)) {
|
|
18
|
+
console.warn(
|
|
19
|
+
`View with id "${view.id}" is already registered. Overwriting.`
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.views.set(view.id, view)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
unregister(viewId: string): void {
|
|
27
|
+
this.views.delete(viewId)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getAllViews(): ViewDefinition[] {
|
|
31
|
+
return Array.from(this.views.values())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
findMatches(
|
|
35
|
+
transaction: DecoderResult,
|
|
36
|
+
options: ViewMatchOptions
|
|
37
|
+
): ViewMatch[] {
|
|
38
|
+
const allViews = this.getAllViews()
|
|
39
|
+
return findMatchingViews(transaction, allViews, options)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getView(viewId: string): ViewDefinition | undefined {
|
|
43
|
+
return this.views.get(viewId)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
clear(): void {
|
|
47
|
+
this.views.clear()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the best matching view (highest score) for a transaction
|
|
52
|
+
*/
|
|
53
|
+
getBestMatch(
|
|
54
|
+
transaction: DecoderResult,
|
|
55
|
+
options: ViewMatchOptions
|
|
56
|
+
): ViewMatch | null {
|
|
57
|
+
const matches = this.findMatches(transaction, { ...options, maxMatches: 1 })
|
|
58
|
+
return matches[0] || null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Bulk register multiple views
|
|
63
|
+
*/
|
|
64
|
+
registerMany(views: ViewDefinition[]): void {
|
|
65
|
+
for (const view of views) {
|
|
66
|
+
this.register(view)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get stats about registered views
|
|
72
|
+
*/
|
|
73
|
+
getStats(): {
|
|
74
|
+
totalViews: number
|
|
75
|
+
viewsByFramework: Record<string, number>
|
|
76
|
+
viewsByRecordType: Record<string, number>
|
|
77
|
+
} {
|
|
78
|
+
const allViews = this.getAllViews()
|
|
79
|
+
const totalViews = allViews.length
|
|
80
|
+
|
|
81
|
+
const viewsByFramework: Record<string, number> = {}
|
|
82
|
+
const viewsByRecordType: Record<string, number> = {}
|
|
83
|
+
|
|
84
|
+
for (const view of allViews) {
|
|
85
|
+
// Count framework support
|
|
86
|
+
for (const framework of Object.keys(view.frameworks)) {
|
|
87
|
+
viewsByFramework[framework] = (viewsByFramework[framework] || 0) + 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Count by record type
|
|
91
|
+
const recordType = view.criteria.recordType
|
|
92
|
+
if (recordType) {
|
|
93
|
+
const types = Array.isArray(recordType) ? recordType : [recordType]
|
|
94
|
+
for (const type of types) {
|
|
95
|
+
viewsByRecordType[type] = (viewsByRecordType[type] || 0) + 1
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
totalViews,
|
|
102
|
+
viewsByFramework,
|
|
103
|
+
viewsByRecordType,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Global registry instance
|
|
110
|
+
*/
|
|
111
|
+
let globalRegistry: ViewRegistry | null = null
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get or create the global view registry
|
|
115
|
+
*/
|
|
116
|
+
export function getGlobalRegistry(): ViewRegistry {
|
|
117
|
+
if (!globalRegistry) {
|
|
118
|
+
globalRegistry = new DefaultViewRegistry()
|
|
119
|
+
}
|
|
120
|
+
return globalRegistry
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set a custom global registry (useful for testing or custom implementations)
|
|
125
|
+
*/
|
|
126
|
+
export function setGlobalRegistry(registry: ViewRegistry): void {
|
|
127
|
+
globalRegistry = registry
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Reset the global registry (mainly for testing)
|
|
132
|
+
*/
|
|
133
|
+
export function resetGlobalRegistry(): void {
|
|
134
|
+
globalRegistry = null
|
|
135
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ViewContext,
|
|
3
|
+
ViewContextRequirements,
|
|
4
|
+
ViewVariant,
|
|
5
|
+
} from './view-contexts'
|
|
6
|
+
export type {
|
|
7
|
+
DecoderRecordForSelection,
|
|
8
|
+
FrameworkSupport,
|
|
9
|
+
FrameworkViewConfig,
|
|
10
|
+
ViewDefinition,
|
|
11
|
+
ViewLoader,
|
|
12
|
+
ViewMatch,
|
|
13
|
+
ViewMatchCriteria,
|
|
14
|
+
ViewMatchOptions,
|
|
15
|
+
ViewRegistry,
|
|
16
|
+
} from './view-matching'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Different contexts WHERE transaction views are displayed
|
|
3
|
+
*
|
|
4
|
+
* Context determines the environment/location, which affects:
|
|
5
|
+
* - Available space
|
|
6
|
+
* - User intent
|
|
7
|
+
* - Security requirements
|
|
8
|
+
* - Interaction patterns
|
|
9
|
+
*/
|
|
10
|
+
export type ViewContext =
|
|
11
|
+
| 'approval' // Transaction approval flow - security focused
|
|
12
|
+
| 'feed' // Activity feed - compact summaries
|
|
13
|
+
| 'list' // Transaction lists - scannable format
|
|
14
|
+
| 'detail' // Full transaction detail - comprehensive
|
|
15
|
+
| 'review' // Pre-submission review - verification focused
|
|
16
|
+
| 'notification' // Push notifications - minimal info
|
|
17
|
+
| 'embed' // Embedded in other interfaces
|
|
18
|
+
| 'batch-summary' // Inside a batch summary display
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Different variants of HOW transaction views are displayed
|
|
22
|
+
*
|
|
23
|
+
* Variant determines the presentation style within a context:
|
|
24
|
+
* - Same transaction can have different variants in different contexts
|
|
25
|
+
* - Example: 'collapsed' in feed vs 'collapsed' in approval are different
|
|
26
|
+
*/
|
|
27
|
+
export type ViewVariant =
|
|
28
|
+
| 'full' // Complete detailed view
|
|
29
|
+
| 'collapsed' // Collapsed/summary view (context-dependent)
|
|
30
|
+
| 'minimal' // Minimal information only
|
|
31
|
+
| 'icon-only' // Just icon/avatar
|
|
32
|
+
| 'compact' // Compact single-line format
|
|
33
|
+
| 'expanded' // Expanded with all details
|
|
34
|
+
| string // Custom variants allowed
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Context-specific display requirements
|
|
38
|
+
*/
|
|
39
|
+
export interface ViewContextRequirements {
|
|
40
|
+
/** Maximum height constraint */
|
|
41
|
+
maxHeight?: number
|
|
42
|
+
/** Whether to show action buttons */
|
|
43
|
+
showActions?: boolean
|
|
44
|
+
/** Level of detail to display */
|
|
45
|
+
detailLevel: 'minimal' | 'summary' | 'full' | 'debug'
|
|
46
|
+
/** Whether user can expand for more details */
|
|
47
|
+
expandable?: boolean
|
|
48
|
+
/** Color scheme preference */
|
|
49
|
+
colorScheme?: 'light' | 'dark' | 'auto'
|
|
50
|
+
/** Whether to emphasize security warnings */
|
|
51
|
+
emphasizeSecurity?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extended view matching criteria with context and variant support
|
|
56
|
+
*/
|
|
57
|
+
export interface ContextAwareViewCriteria {
|
|
58
|
+
/** Standard matching criteria */
|
|
59
|
+
recordType?: string | string[]
|
|
60
|
+
functionName?: string | string[]
|
|
61
|
+
standard?: string | string[]
|
|
62
|
+
contractAddress?: string | string[]
|
|
63
|
+
chainId?: number | number[]
|
|
64
|
+
customMatcher?: (transaction: any) => boolean
|
|
65
|
+
|
|
66
|
+
/** Context-specific matching (WHERE to display) */
|
|
67
|
+
context?: ViewContext | ViewContext[]
|
|
68
|
+
/** Variant-specific matching (HOW to display) */
|
|
69
|
+
variant?: ViewVariant | ViewVariant[]
|
|
70
|
+
/** Specific context requirements */
|
|
71
|
+
contextRequirements?: Partial<ViewContextRequirements>
|
|
72
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
|
|
3
|
+
// Type for view selection with all possible decoder properties as optional
|
|
4
|
+
// The decoder conditionally adds these based on transaction type
|
|
5
|
+
export type DecoderRecordForSelection = DecoderResult & {
|
|
6
|
+
recordType?: string
|
|
7
|
+
functionName?: string
|
|
8
|
+
contractAddress?: string
|
|
9
|
+
standard?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ViewContext,
|
|
14
|
+
ViewContextRequirements,
|
|
15
|
+
ViewVariant,
|
|
16
|
+
} from './view-contexts'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Criteria that can be used to match transactions to views
|
|
20
|
+
*/
|
|
21
|
+
export interface ViewMatchCriteria {
|
|
22
|
+
/** LSP standard (e.g., 'LSP7', 'LSP8', 'LSP0') */
|
|
23
|
+
standard?: string | string[]
|
|
24
|
+
/** Function name from ABI (e.g., 'transfer', 'setData', 'execute') */
|
|
25
|
+
functionName?: string | string[]
|
|
26
|
+
/** Record type from decoder (e.g., 'lsp7-transfer', 'set-data') */
|
|
27
|
+
recordType?: string | string[]
|
|
28
|
+
/** Contract address (for specific contract implementations) */
|
|
29
|
+
contractAddress?: string | string[]
|
|
30
|
+
/** Chain ID (for network-specific views) */
|
|
31
|
+
chainId?: number | number[]
|
|
32
|
+
/** Display context (opt-in) - WHERE to display (feed, approval, list, etc.) */
|
|
33
|
+
context?: ViewContext | ViewContext[]
|
|
34
|
+
/** Display variant (opt-in) - HOW to display (collapsed, minimal, full, etc.) */
|
|
35
|
+
variant?: ViewVariant | ViewVariant[]
|
|
36
|
+
/** Context requirements (opt-in) - additional constraints for context matching */
|
|
37
|
+
contextRequirements?: Partial<ViewContextRequirements>
|
|
38
|
+
/**
|
|
39
|
+
* Custom matcher function for complex synchronous logic.
|
|
40
|
+
* Returns the number of conditions that matched (higher = better match).
|
|
41
|
+
*
|
|
42
|
+
* IMPORTANT: Must be synchronous and only use data from DecoderResult.
|
|
43
|
+
* For async operations, populate transaction.custom in an enhancer.
|
|
44
|
+
*
|
|
45
|
+
* @returns Number of matching conditions (0 = no match, higher = stronger match)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Match token transfers (tokenType === 0)
|
|
49
|
+
* customMatcher: (tx) => {
|
|
50
|
+
* let matches = 0
|
|
51
|
+
* if (tx.custom?.tokenType === 0) matches++
|
|
52
|
+
* if (BigInt(tx.value || 0) > 0) matches++
|
|
53
|
+
* return matches
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
customMatcher?: (transaction: DecoderResult) => number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Framework-agnostic view definition
|
|
61
|
+
*/
|
|
62
|
+
export interface ViewDefinition {
|
|
63
|
+
/** Unique identifier for this view */
|
|
64
|
+
id: string
|
|
65
|
+
/** Human-readable name */
|
|
66
|
+
name: string
|
|
67
|
+
/** Criteria for matching transactions to this view */
|
|
68
|
+
criteria: ViewMatchCriteria
|
|
69
|
+
/** Priority when multiple views match (higher = preferred) */
|
|
70
|
+
priority: number
|
|
71
|
+
/** Supported frameworks for this view */
|
|
72
|
+
frameworks: FrameworkSupport
|
|
73
|
+
/** Optional description */
|
|
74
|
+
description?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Framework-specific loading configurations
|
|
79
|
+
*
|
|
80
|
+
* Strategic Framework Selection (Suffering Economics Optimized):
|
|
81
|
+
* - lit: Universal web components (works in Vue, React, Angular, Svelte, etc.)
|
|
82
|
+
* - vue: Native Vue components (optional, for Vue-specific optimizations)
|
|
83
|
+
* - rn: React Native components (mobile-native, optional - falls back to WebView)
|
|
84
|
+
*
|
|
85
|
+
* Lit is the universal fallback. Vue and React Native are optional native implementations
|
|
86
|
+
* that get priority when available in their respective environments.
|
|
87
|
+
*/
|
|
88
|
+
export interface FrameworkSupport {
|
|
89
|
+
lit: FrameworkViewConfig // Web Components (universal web compatibility) - REQUIRED
|
|
90
|
+
vue?: FrameworkViewConfig // Vue 3 components (native Vue, optional)
|
|
91
|
+
rn?: FrameworkViewConfig // React Native JSX (mobile-native, optional)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Configuration for loading a view in a specific framework
|
|
96
|
+
*/
|
|
97
|
+
export interface FrameworkViewConfig {
|
|
98
|
+
/** Loading method */
|
|
99
|
+
loader: ViewLoader
|
|
100
|
+
/** Component name/identifier */
|
|
101
|
+
component: string
|
|
102
|
+
/** Package where the component is located */
|
|
103
|
+
package?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Different ways to load a view component
|
|
108
|
+
*
|
|
109
|
+
* Loaders support both static and lazy loading:
|
|
110
|
+
* - Static: `path: './Component.vue'` - bundled with app
|
|
111
|
+
* - Lazy: `path: () => import('./Component.vue')` - loaded on demand
|
|
112
|
+
*/
|
|
113
|
+
export type ViewLoader =
|
|
114
|
+
| { type: 'dynamic-import'; path: string | (() => Promise<any>) }
|
|
115
|
+
| { type: 'static-import'; path: string | (() => Promise<any>) }
|
|
116
|
+
| { type: 'custom-element'; tagName: string }
|
|
117
|
+
| { type: 'registry-lookup'; registryKey: string }
|
|
118
|
+
| { type: 'custom'; loader: (config: FrameworkViewConfig) => Promise<any> }
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Result of matching a transaction against registered views
|
|
122
|
+
*/
|
|
123
|
+
export interface ViewMatch {
|
|
124
|
+
/** The view definition that matched */
|
|
125
|
+
view: ViewDefinition
|
|
126
|
+
/** Score indicating how well this view matches (0-1) */
|
|
127
|
+
score: number
|
|
128
|
+
/** Which criteria matched */
|
|
129
|
+
matchedCriteria: (keyof ViewMatchCriteria)[]
|
|
130
|
+
/** Framework-specific loading config */
|
|
131
|
+
frameworkConfig?: FrameworkViewConfig
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Options for view matching
|
|
136
|
+
*/
|
|
137
|
+
export interface ViewMatchOptions {
|
|
138
|
+
/** Target framework */
|
|
139
|
+
framework: 'lit' | 'vue' | 'rn'
|
|
140
|
+
/** Display context (optional) - WHERE to display (feed, approval, list, etc.) */
|
|
141
|
+
context?: ViewContext
|
|
142
|
+
/** Display variant (optional) - HOW to display (collapsed, minimal, full, etc.) */
|
|
143
|
+
variant?: ViewVariant
|
|
144
|
+
/** Context requirements (optional) - additional constraints for context-aware matching */
|
|
145
|
+
contextRequirements?: Partial<ViewContextRequirements>
|
|
146
|
+
/** Minimum score threshold (0-1) */
|
|
147
|
+
minScore?: number
|
|
148
|
+
/** Maximum number of matches to return */
|
|
149
|
+
maxMatches?: number
|
|
150
|
+
/** Whether to include framework config in results */
|
|
151
|
+
includeFrameworkConfig?: boolean
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Registry for managing view definitions
|
|
156
|
+
*/
|
|
157
|
+
export interface ViewRegistry {
|
|
158
|
+
/** Register a new view */
|
|
159
|
+
register(view: ViewDefinition): void
|
|
160
|
+
/** Unregister a view by ID */
|
|
161
|
+
unregister(viewId: string): void
|
|
162
|
+
/** Get all registered views */
|
|
163
|
+
getAllViews(): ViewDefinition[]
|
|
164
|
+
/** Find best matching views for a transaction */
|
|
165
|
+
findMatches(
|
|
166
|
+
transaction: DecoderResult,
|
|
167
|
+
options: ViewMatchOptions
|
|
168
|
+
): ViewMatch[]
|
|
169
|
+
/** Get a specific view by ID */
|
|
170
|
+
getView(viewId: string): ViewDefinition | undefined
|
|
171
|
+
/** Clear all registered views */
|
|
172
|
+
clear(): void
|
|
173
|
+
}
|