@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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +33 -0
  2. package/CHANGELOG.md +104 -0
  3. package/LICENSE +201 -0
  4. package/README.md +264 -0
  5. package/dist/index.cjs +4573 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +493 -0
  8. package/dist/index.d.ts +493 -0
  9. package/dist/index.js +4534 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/shared/index.cjs +2480 -0
  12. package/dist/shared/index.cjs.map +1 -0
  13. package/dist/shared/index.d.cts +887 -0
  14. package/dist/shared/index.d.ts +887 -0
  15. package/dist/shared/index.js +2437 -0
  16. package/dist/shared/index.js.map +1 -0
  17. package/dist/styles/index.cjs +58 -0
  18. package/dist/styles/index.cjs.map +1 -0
  19. package/dist/styles/index.d.cts +9 -0
  20. package/dist/styles/index.d.ts +9 -0
  21. package/dist/styles/index.js +31 -0
  22. package/dist/styles/index.js.map +1 -0
  23. package/package.json +57 -0
  24. package/publish.log +0 -0
  25. package/src/components/asset-card.ts +426 -0
  26. package/src/components/contract-card.ts +238 -0
  27. package/src/components/description-view.ts +605 -0
  28. package/src/components/index.ts +20 -0
  29. package/src/components/info-panel.ts +437 -0
  30. package/src/components/permissions-block.ts +138 -0
  31. package/src/components/transaction-playback.ts +429 -0
  32. package/src/components/universal-token-view.ts +281 -0
  33. package/src/declaration.d.ts +4 -0
  34. package/src/examples/INTEGRATION_GUIDE.md +240 -0
  35. package/src/examples/TransactionContentWithReplacement.vue +269 -0
  36. package/src/index.ts +38 -0
  37. package/src/registry/index.ts +31 -0
  38. package/src/registry/lit-view-loader.ts +191 -0
  39. package/src/shared/address-display.ts +370 -0
  40. package/src/shared/address-view.ts +385 -0
  41. package/src/shared/custom-select.ts +164 -0
  42. package/src/shared/function-name-view.ts +89 -0
  43. package/src/shared/gas-provider-view.ts +218 -0
  44. package/src/shared/icon-view.ts +288 -0
  45. package/src/shared/image-list.ts +261 -0
  46. package/src/shared/image-loader-controller.ts +160 -0
  47. package/src/shared/index.ts +34 -0
  48. package/src/shared/load-web-components.ts +56 -0
  49. package/src/shared/notification-view.ts +132 -0
  50. package/src/shared/progress-view.ts +86 -0
  51. package/src/shared/safe-custom-element.ts +35 -0
  52. package/src/shared/scroll-to-bottom-button.ts +55 -0
  53. package/src/shared/signal-controller.ts +124 -0
  54. package/src/shared/transaction-view-controller.ts +345 -0
  55. package/src/shared/url-view.ts +129 -0
  56. package/src/shared/value-view.ts +510 -0
  57. package/src/styles/index.ts +28 -0
  58. package/src/styles/styles.css +1 -0
  59. package/src/vue-entry.ts +15 -0
  60. package/tailwind.config.cjs +9 -0
  61. package/tsconfig.json +22 -0
  62. package/tsup.config.ts +90 -0
@@ -0,0 +1,261 @@
1
+ import { Task } from '@lit/task'
2
+ import { useAddressResolver } from '@lukso/transaction-view-headless'
3
+ import { html } from 'lit'
4
+ import { customElement, property, state } from 'lit/decorators.js'
5
+ import { repeat } from 'lit/directives/repeat.js'
6
+ import { safeCustomElement } from './safe-custom-element'
7
+ import './icon-view.js'
8
+ import { CoreLitElement } from '../styles'
9
+
10
+ /**
11
+ * Image list component for displaying arrays of images/assets
12
+ *
13
+ * Displays a list of images from a resolved address property (e.g., 'images', 'assets').
14
+ * If items have an 'index' property, shows only items with matching indices.
15
+ * Handles different file types (images, videos, audio, generic files).
16
+ *
17
+ * @example
18
+ * ```html
19
+ * <!-- Display all images -->
20
+ * <image-list
21
+ * address="0x1234..."
22
+ * image-hint="images"
23
+ * ></image-list>
24
+ *
25
+ * <!-- Display images with index 0-5 -->
26
+ * <image-list
27
+ * address="0x1234..."
28
+ * image-hint="images"
29
+ * min-index="0"
30
+ * max-index="5"
31
+ * ></image-list>
32
+ * ```
33
+ */
34
+ @safeCustomElement('image-list')
35
+ export class ImageList extends CoreLitElement {
36
+ /**
37
+ * The address to display (required)
38
+ */
39
+ @property() address!: string
40
+
41
+ /**
42
+ * Property name to use for loading images (e.g., 'images', 'assets')
43
+ * This should point to an array property on the resolved data
44
+ */
45
+ @property({ type: String, attribute: 'image-hint' }) imageHint!: string
46
+
47
+ /**
48
+ * Minimum index to display (only applies if items have index property)
49
+ */
50
+ @property({ type: Number, attribute: 'min-index' }) minIndex?: number
51
+
52
+ /**
53
+ * Maximum index to display (only applies if items have index property)
54
+ */
55
+ @property({ type: Number, attribute: 'max-index' }) maxIndex?: number
56
+
57
+ /**
58
+ * Display size for images
59
+ */
60
+ @property() size: 'x-small' | 'small' | 'medium' | 'large' = 'small'
61
+
62
+ /**
63
+ * Optional card view: wrap list in a bordered, padded container
64
+ */
65
+ @property({ type: Boolean, attribute: 'is-card-view' }) isCardView = false
66
+
67
+ // Internal state
68
+ @state() private isLoading = false
69
+
70
+ // Composables
71
+ private addressResolver = useAddressResolver()
72
+
73
+ // Task for resolving address data
74
+ private addressTask = new Task(this, {
75
+ task: async ([address], { signal }) => {
76
+ if (!address) return null
77
+
78
+ this.isLoading = true
79
+ try {
80
+ const resolved = await this.addressResolver.resolveAddress(address)
81
+ return resolved
82
+ } catch (error) {
83
+ console.warn('❌ ImageList: Failed to resolve address:', error)
84
+ return null
85
+ } finally {
86
+ this.isLoading = false
87
+ }
88
+ },
89
+ args: () => [this.address],
90
+ })
91
+
92
+ /**
93
+ * Get the list of items to display from resolved data
94
+ */
95
+ private getDisplayItems(): any[] {
96
+ const resolved = this.addressTask.value
97
+ if (!resolved) return []
98
+ console.log('resolved images are', resolved)
99
+ const items = (resolved as any)[this.imageHint]
100
+ if (!Array.isArray(items)) return []
101
+
102
+ // Check if items have an index property
103
+ const hasIndex = items.length > 0 && items[0]?.index !== undefined
104
+
105
+ if (!hasIndex) {
106
+ // No index property, return all items
107
+ return items
108
+ }
109
+
110
+ // Filter by index range
111
+ return items.filter((item) => {
112
+ const index = item.index
113
+ if (this.minIndex !== undefined && index < this.minIndex) return false
114
+ if (this.maxIndex !== undefined && index > this.maxIndex) return false
115
+ return true
116
+ })
117
+ }
118
+
119
+ /**
120
+ * Check if an item is an actual image based on fileType
121
+ */
122
+ private isImageFile(item: any): boolean {
123
+ if (!item.fileType) return true // Assume image if no fileType
124
+ return item.fileType.startsWith('image/')
125
+ }
126
+
127
+ /**
128
+ * Get file type icon for non-image files
129
+ */
130
+ private getFileTypeIcon(item: any): string {
131
+ if (!item.fileType) return '📄'
132
+
133
+ if (item.fileType.startsWith('video/')) return '🎥'
134
+ if (item.fileType.startsWith('audio/')) return '🎵'
135
+ if (item.fileType.startsWith('application/pdf')) return '📕'
136
+ return '📄'
137
+ }
138
+
139
+ /**
140
+ * Render a single image item
141
+ */
142
+ private renderImageItem(item: any, index: number) {
143
+ const itemIndex = item.index !== undefined ? item.index : index
144
+ const isImage = this.isImageFile(item)
145
+
146
+ if (isImage) {
147
+ // Show broken image placeholder if image doesn't exist or can't load
148
+ const imageSrc = item.src || item.url
149
+
150
+ if (!imageSrc) {
151
+ return html`
152
+ <div class="image-item broken-image-item">
153
+ <div class="broken-image-icon">🖼️❌</div>
154
+ ${
155
+ item.index !== undefined
156
+ ? html`<div class="broken-image-label">#${item.index}</div>`
157
+ : ''
158
+ }
159
+ <div class="broken-image-text">No image</div>
160
+ </div>
161
+ `
162
+ }
163
+
164
+ // Use direct image display instead of icon-view to avoid identicons
165
+ return html`
166
+ <div class="">
167
+ <img
168
+ class="min-h-20 min-w-20 object-cover rounded-lg"
169
+ src=${imageSrc}
170
+ alt=${item.index !== undefined ? `#${item.index}` : 'Image'}
171
+ @error=${(e: Event) => {
172
+ const target = e.target as HTMLImageElement
173
+ target.style.display = 'none'
174
+ const parent = target.parentElement
175
+ if (parent) {
176
+ parent.innerHTML = `
177
+ <div class="broken-image-icon">🖼️❌</div>
178
+ <div class="broken-image-text">Failed to load</div>
179
+ `
180
+ }
181
+ }}
182
+ />
183
+ ${
184
+ item.index !== undefined
185
+ ? html`<div class="image-label">#${item.index}</div>`
186
+ : ''
187
+ }
188
+ </div>
189
+ `
190
+ } else {
191
+ // Show file type icon for non-images
192
+ return html`
193
+ <div class="image-item file-item">
194
+ <div class="file-icon">${this.getFileTypeIcon(item)}</div>
195
+ ${
196
+ item.index !== undefined
197
+ ? html`<div class="file-label">#${item.index}</div>`
198
+ : ''
199
+ }
200
+ <div class="file-type">${item.fileType || 'Unknown'}</div>
201
+ </div>
202
+ `
203
+ }
204
+ }
205
+
206
+ private getImageMinSizePx(): number {
207
+ switch (this.size) {
208
+ case 'x-small':
209
+ return 80
210
+ case 'small':
211
+ return 120
212
+ case 'medium':
213
+ return 160
214
+ case 'large':
215
+ return 240
216
+ default:
217
+ return 160
218
+ }
219
+ }
220
+
221
+ override render() {
222
+ if (this.isLoading) {
223
+ return html`
224
+ <div class="flex flex-col items-center justify-center py-10 px-5 text-neutral-60">
225
+ <div class="text-[24px] mb-2 animate-spin">⏳</div>
226
+ <div class="text-[14px]">Loading images...</div>
227
+ </div>
228
+ `
229
+ }
230
+
231
+ const items = this.getDisplayItems()
232
+
233
+ if (items.length === 0) {
234
+ return html`
235
+ <div class="flex flex-col items-center justify-center py-10 px-5 text-neutral-70">
236
+ <div class="text-[14px]">No images found</div>
237
+ </div>
238
+ `
239
+ }
240
+
241
+ const grid = html`
242
+ <div class="grid grid-cols-[repeat(auto-fill,minmax(${this.getImageMinSizePx()}px,300px))] gap-3 p-2">
243
+ ${repeat(
244
+ items,
245
+ (item, index) => item.index ?? index,
246
+ (item, index) => this.renderImageItem(item, index)
247
+ )}
248
+ </div>
249
+ `
250
+
251
+ return this.isCardView
252
+ ? html`<div class="flex flex-row flex-wrap border border-neutral-90 rounded-xl p-3 bg-neutral-100">${grid}</div>`
253
+ : grid
254
+ }
255
+ }
256
+
257
+ declare global {
258
+ interface HTMLElementTagNameMap {
259
+ 'image-list': ImageList
260
+ }
261
+ }
@@ -0,0 +1,160 @@
1
+ import {
2
+ type AddressImageData,
3
+ type ImageLoadOptions,
4
+ type ImageResult,
5
+ useImageLoader as useImageLoaderHeadless,
6
+ } from '@lukso/transaction-view-headless'
7
+ import type { Signal } from '@preact/signals-core'
8
+ import type { ReactiveController, ReactiveControllerHost } from 'lit'
9
+
10
+ /**
11
+ * Lit controller state for image loading
12
+ */
13
+ export interface ImageLoadState {
14
+ result: ImageResult | null
15
+ loading: boolean
16
+ error: string | null
17
+ }
18
+
19
+ /**
20
+ * Lit Reactive Controller for image loading functionality
21
+ * Integrates the headless image loader with Lit elements
22
+ */
23
+ export class ImageLoaderController implements ReactiveController {
24
+ private host: ReactiveControllerHost
25
+ private headlessLoader = useImageLoaderHeadless()
26
+
27
+ // Internal state
28
+ private _state: ImageLoadState = {
29
+ result: null,
30
+ loading: false,
31
+ error: null,
32
+ }
33
+
34
+ constructor(host: ReactiveControllerHost) {
35
+ this.host = host
36
+ host.addController(this)
37
+ }
38
+
39
+ hostConnected() {
40
+ // Controller is ready
41
+ }
42
+
43
+ hostDisconnected() {
44
+ // Cleanup if needed
45
+ }
46
+
47
+ get state(): ImageLoadState {
48
+ return this._state
49
+ }
50
+
51
+ get isLoading(): boolean {
52
+ return this._state.loading
53
+ }
54
+
55
+ get error(): string | null {
56
+ return this._state.error
57
+ }
58
+
59
+ get result(): ImageResult | null {
60
+ return this._state.result
61
+ }
62
+
63
+ /**
64
+ * Load image from data array
65
+ */
66
+ async loadImage(
67
+ imageData: any[],
68
+ options: ImageLoadOptions = {}
69
+ ): Promise<ImageResult | null> {
70
+ this.updateState({ loading: true, error: null })
71
+
72
+ try {
73
+ const result = await this.headlessLoader.loadImage(imageData, options)
74
+ this.updateState({ result, loading: false })
75
+ return result
76
+ } catch (err) {
77
+ const error = err instanceof Error ? err.message : 'Failed to load image'
78
+ this.updateState({ loading: false, error })
79
+ return null
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Load profile image from address data
85
+ */
86
+ async loadProfileImage(
87
+ addressData: AddressImageData,
88
+ options: ImageLoadOptions = {}
89
+ ): Promise<ImageResult | null> {
90
+ this.updateState({ loading: true, error: null })
91
+
92
+ try {
93
+ const result =
94
+ (await this.headlessLoader.loadProfileImage(addressData, options)) ||
95
+ this.headlessLoader.getFallbackImage(addressData)
96
+
97
+ this.updateState({ result, loading: false })
98
+ return result
99
+ } catch (err) {
100
+ const error =
101
+ err instanceof Error ? err.message : 'Failed to load profile image'
102
+ const fallback = this.headlessLoader.getFallbackImage(addressData)
103
+ this.updateState({ result: fallback, loading: false, error })
104
+ return fallback
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Load image from address signal (reactive)
110
+ */
111
+ loadFromSignal(
112
+ addressSignal: Signal<{ data?: AddressImageData }>,
113
+ options: ImageLoadOptions = {}
114
+ ): void {
115
+ const addressData = addressSignal.value?.data
116
+
117
+ if (!addressData) {
118
+ const fallback = this.headlessLoader.getFallbackImage({})
119
+ this.updateState({ result: fallback, loading: false, error: null })
120
+ return
121
+ }
122
+
123
+ this.loadProfileImage(addressData, options)
124
+ }
125
+
126
+ /**
127
+ * Get fallback image synchronously
128
+ */
129
+ getFallbackImage(addressData: AddressImageData): ImageResult {
130
+ return this.headlessLoader.getFallbackImage(addressData)
131
+ }
132
+
133
+ /**
134
+ * Check if address is a contract
135
+ */
136
+ isContractAddress(addressData: AddressImageData): boolean {
137
+ return this.headlessLoader.isContractAddress(addressData)
138
+ }
139
+
140
+ /**
141
+ * Clear current state
142
+ */
143
+ clear(): void {
144
+ this.updateState({ result: null, loading: false, error: null })
145
+ }
146
+
147
+ private updateState(updates: Partial<ImageLoadState>): void {
148
+ this._state = { ...this._state, ...updates }
149
+ this.host.requestUpdate()
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Utility function to create an image loader controller
155
+ */
156
+ export function useImageLoader(
157
+ host: ReactiveControllerHost
158
+ ): ImageLoaderController {
159
+ return new ImageLoaderController(host)
160
+ }
@@ -0,0 +1,34 @@
1
+ // Shared atomic components
2
+
3
+ export * from './address-display'
4
+ // Export types
5
+ export type { AddressData } from './address-display.js'
6
+ export * from './address-view'
7
+ export * from './custom-select'
8
+ export * from './function-name-view'
9
+ export * from './gas-provider-view'
10
+ export * from './icon-view'
11
+ export {
12
+ ImageLoaderController,
13
+ type ImageLoadState,
14
+ useImageLoader,
15
+ } from './image-loader-controller.js'
16
+ export * from './notification-view'
17
+ export * from './progress-view'
18
+ export * from './scroll-to-bottom-button'
19
+ // Export signal integration controllers and utilities
20
+ export {
21
+ MultiSignalController,
22
+ SignalController,
23
+ useSignal,
24
+ useSignals,
25
+ } from './signal-controller.js'
26
+ export {
27
+ TransactionSignalController,
28
+ TransactionViewController,
29
+ type TransactionViewState,
30
+ useTransactionSignal,
31
+ useTransactionView,
32
+ } from './transaction-view-controller.js'
33
+ export * from './url-view'
34
+ export * from './value-view'
@@ -0,0 +1,56 @@
1
+ /**
2
+ * SSR-safe centralized import point for @lukso/web-components.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * Web components crash during SSR, so apps use dynamic imports:
7
+ * ```ts
8
+ * // In an SSR app
9
+ * const { TransactionPlayback } = await import('@lukso/transaction-view-core')
10
+ * ```
11
+ *
12
+ * However, if the app ALSO needs to import @lukso/web-components directly:
13
+ * ```ts
14
+ * const webComponents = await import('@lukso/web-components')
15
+ * ```
16
+ *
17
+ * This creates TWO dynamic import paths to the same components, causing
18
+ * duplicate registration errors. This wrapper prevents that by making
19
+ * registration idempotent.
20
+ *
21
+ * ## Usage
22
+ *
23
+ * Import this module instead of directly importing '@lukso/web-components':
24
+ * ```ts
25
+ * import './load-web-components'
26
+ * ```
27
+ */
28
+
29
+ // Store the original customElements.define
30
+ const originalDefine =
31
+ typeof customElements !== 'undefined'
32
+ ? customElements.define.bind(customElements)
33
+ : null
34
+
35
+ // Wrap customElements.define to catch duplicate registration errors
36
+ if (typeof customElements !== 'undefined' && originalDefine) {
37
+ customElements.define = function (
38
+ name: string,
39
+ constructor: CustomElementConstructor,
40
+ options?: ElementDefinitionOptions
41
+ ) {
42
+ // Check if already defined
43
+ if (!customElements.get(name)) {
44
+ originalDefine(name, constructor, options)
45
+ }
46
+ // Silently ignore if already defined
47
+ }
48
+ }
49
+
50
+ // Now import the web components (they'll use our wrapped define)
51
+ import './load-web-components'
52
+
53
+ // Restore original define after import
54
+ if (originalDefine && typeof customElements !== 'undefined') {
55
+ customElements.define = originalDefine
56
+ }
@@ -0,0 +1,132 @@
1
+ import { html } from 'lit'
2
+ import { customElement, property, state } from 'lit/decorators.js'
3
+ import { safeCustomElement } from './safe-custom-element'
4
+ import './load-web-components'
5
+ import { CoreLitElement } from '../styles'
6
+
7
+ @safeCustomElement('notification-view')
8
+ export class NotificationView extends CoreLitElement {
9
+ /** Variant controls background/emphasis */
10
+ @property() variant: 'success' | 'warning' | 'error' | 'info' = 'info'
11
+
12
+ /** Notification message content */
13
+ @property() message = ''
14
+
15
+ /** Optional title shown above the description */
16
+ @property() title = ''
17
+
18
+ /** Whether the acknowledge button is shown */
19
+ @property({ type: Boolean, attribute: 'acknowledgeable' }) acknowledgeable =
20
+ false
21
+
22
+ /** Text shown next to acknowledge checkbox */
23
+ @property({ attribute: 'acknowledge-text' }) acknowledgeText =
24
+ 'I understand and want to continue'
25
+
26
+ /** Internal acknowledged state (for default behavior) */
27
+ @state() private acknowledged = false
28
+
29
+ /** External control for acknowledge checkbox */
30
+ @property({ type: Boolean, reflect: true }) checked = false
31
+
32
+ private handleAcknowledgeChange(e: Event) {
33
+ e.stopPropagation()
34
+ const target: any = e.target as any
35
+ const checked = !!(target?.checked ?? (e as any).detail?.checked ?? false)
36
+ if (checked !== this.checked) {
37
+ this.checked = checked
38
+ }
39
+ if (this.checked) {
40
+ this.acknowledged = true
41
+ this.dispatchEvent(
42
+ new CustomEvent('notification-acknowledged', {
43
+ detail: {
44
+ variant: this.variant,
45
+ message: this.message,
46
+ title: this.title,
47
+ checked: this.checked,
48
+ },
49
+ bubbles: true,
50
+ })
51
+ )
52
+ }
53
+ }
54
+
55
+ override render() {
56
+ if (this.acknowledged) return html``
57
+
58
+ return html`
59
+ <div class="grid grid-cols-[24px_1fr] items-start gap-3 p-3 rounded-xl text-neutral-10 leading-snug ${this.getVariantBackgroundClass()}">
60
+ <div class="flex items-center justify-center text-[16px] text-neutral-20 self-center" aria-hidden="true">
61
+ <lukso-icon name="${this.renderIcon()}" size="large" color="${this.getVariantColor()}"></lukso-icon>
62
+ </div>
63
+ <div class="text-[14px] flex flex-col gap-1.5">
64
+ ${
65
+ this.title
66
+ ? html`<div class="font-semibold text-neutral-20 paragraph-inter-14-semi-bold">${this.title}</div>`
67
+ : ''
68
+ }
69
+ <div class="text-neutral-30"><slot>${this.message}</slot></div>
70
+ ${
71
+ this.acknowledgeable
72
+ ? html`
73
+ <div class="inline-flex items-center gap-2 mt-1 text-[12px] text-neutral-20 select-none cursor-pointer paragraph-inter-12-medium">
74
+ <lukso-checkbox
75
+ size="x-small"
76
+ ?checked=${this.checked}
77
+ @change=${this.handleAcknowledgeChange}
78
+ ><span class="paragraph-inter-14-semi-bold">${this.acknowledgeText}</span></lukso-checkbox>
79
+ </div>
80
+ `
81
+ : ''
82
+ }
83
+ </div>
84
+ </div>
85
+ `
86
+ }
87
+
88
+ private renderIcon() {
89
+ switch (this.variant) {
90
+ case 'success':
91
+ return 'bulb-outline'
92
+ case 'warning':
93
+ return 'warning-round'
94
+ case 'error':
95
+ return 'warning-triangle'
96
+ default:
97
+ return 'bulb-outline'
98
+ }
99
+ }
100
+
101
+ private getVariantColor() {
102
+ switch (this.variant) {
103
+ case 'success':
104
+ return 'green-45'
105
+ case 'warning':
106
+ return 'yellow-55'
107
+ case 'error':
108
+ return 'red-55'
109
+ default:
110
+ return 'blue-50'
111
+ }
112
+ }
113
+
114
+ private getVariantBackgroundClass() {
115
+ switch (this.variant) {
116
+ case 'success':
117
+ return 'bg-green-85'
118
+ case 'warning':
119
+ return 'bg-yellow-85'
120
+ case 'error':
121
+ return 'bg-red-85'
122
+ default:
123
+ return 'bg-blue-85'
124
+ }
125
+ }
126
+ }
127
+
128
+ declare global {
129
+ interface HTMLElementTagNameMap {
130
+ 'notification-view': NotificationView
131
+ }
132
+ }