@quicktvui/web-renderer 1.0.0
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/package.json +24 -0
- package/src/adapters/es3-video-player.js +828 -0
- package/src/components/Modal.js +119 -0
- package/src/components/QtAnimationView.js +678 -0
- package/src/components/QtBaseComponent.js +165 -0
- package/src/components/QtFastListView.js +1920 -0
- package/src/components/QtFlexView.js +799 -0
- package/src/components/QtImage.js +203 -0
- package/src/components/QtItemFrame.js +239 -0
- package/src/components/QtItemStoreView.js +93 -0
- package/src/components/QtItemView.js +125 -0
- package/src/components/QtListView.js +331 -0
- package/src/components/QtLoadingView.js +55 -0
- package/src/components/QtPageRootView.js +19 -0
- package/src/components/QtPlayMark.js +168 -0
- package/src/components/QtProgressBar.js +199 -0
- package/src/components/QtQRCode.js +78 -0
- package/src/components/QtReplaceChild.js +149 -0
- package/src/components/QtRippleView.js +166 -0
- package/src/components/QtSeekBar.js +409 -0
- package/src/components/QtText.js +679 -0
- package/src/components/QtTransitionImage.js +170 -0
- package/src/components/QtView.js +706 -0
- package/src/components/QtWebView.js +613 -0
- package/src/components/TabsView.js +420 -0
- package/src/components/ViewPager.js +206 -0
- package/src/components/index.js +24 -0
- package/src/components/plugins/TextV2Component.js +70 -0
- package/src/components/plugins/index.js +7 -0
- package/src/core/SceneBuilder.js +58 -0
- package/src/core/TVFocusManager.js +2014 -0
- package/src/core/asyncLocalStorage.js +175 -0
- package/src/core/autoProxy.js +165 -0
- package/src/core/componentRegistry.js +84 -0
- package/src/core/constants.js +6 -0
- package/src/core/index.js +8 -0
- package/src/core/moduleUtils.js +36 -0
- package/src/core/patches.js +958 -0
- package/src/core/templateBinding.js +666 -0
- package/src/index.js +246 -0
- package/src/modules/AndroidDevelopModule.js +101 -0
- package/src/modules/AndroidDeviceModule.js +341 -0
- package/src/modules/AndroidNetworkModule.js +178 -0
- package/src/modules/AndroidSharedPreferencesModule.js +100 -0
- package/src/modules/ESDeviceInfoModule.js +450 -0
- package/src/modules/ESGroupDataModule.js +195 -0
- package/src/modules/ESIJKAudioPlayerModule.js +477 -0
- package/src/modules/ESLocalStorageModule.js +100 -0
- package/src/modules/ESLogModule.js +65 -0
- package/src/modules/ESModule.js +106 -0
- package/src/modules/ESNetworkSpeedModule.js +117 -0
- package/src/modules/ESToastModule.js +172 -0
- package/src/modules/EsNativeModule.js +117 -0
- package/src/modules/FastListModule.js +101 -0
- package/src/modules/FocusModule.js +145 -0
- package/src/modules/RuntimeDeviceModule.js +176 -0
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
// Patches for web renderer
|
|
2
|
+
// Contains various patches and fixes for Hippy web-renderer
|
|
3
|
+
|
|
4
|
+
import { setWebEngine, findComponentById } from './componentRegistry'
|
|
5
|
+
import { TV_WIDTH, TV_HEIGHT } from './constants'
|
|
6
|
+
import { AndroidDeviceModule } from '../modules/AndroidDeviceModule'
|
|
7
|
+
import { androidNetworkModuleInstance as AndroidNetworkModule } from '../modules/AndroidNetworkModule'
|
|
8
|
+
import { runtimeDeviceModuleInstance as RuntimeDeviceModule } from '../modules/RuntimeDeviceModule'
|
|
9
|
+
import { esToastModuleInstance as ESToastModule } from '../modules/ESToastModule'
|
|
10
|
+
import { ESIJKAudioPlayerModule } from '../modules/ESIJKAudioPlayerModule'
|
|
11
|
+
import { esNetworkSpeedModuleInstance as ESNetworkSpeedModule } from '../modules/ESNetworkSpeedModule'
|
|
12
|
+
|
|
13
|
+
// Create singleton instance for audio player
|
|
14
|
+
const audioPlayerModule = new ESIJKAudioPlayerModule(null)
|
|
15
|
+
global.__WEB_MODULES__ = global.__WEB_MODULES__ || {}
|
|
16
|
+
global.__WEB_MODULES__['ESIJKAudioPlayerModule'] = audioPlayerModule
|
|
17
|
+
global.__WEB_MODULES__['ESNetworkSpeedModule'] = ESNetworkSpeedModule
|
|
18
|
+
|
|
19
|
+
// Track root node ID
|
|
20
|
+
let rootNodeId = null
|
|
21
|
+
let callNativePatched = false
|
|
22
|
+
let hippyCallNativesPatched = false
|
|
23
|
+
|
|
24
|
+
export function patchHippyCallNatives(engine) {
|
|
25
|
+
if (hippyCallNativesPatched) {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
const original = global.hippyCallNatives
|
|
29
|
+
if (typeof original !== 'function') {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const storageMethods = new Set([
|
|
34
|
+
'initSharedPreferences',
|
|
35
|
+
'initESSharedPreferences',
|
|
36
|
+
'getBoolean',
|
|
37
|
+
'putBoolean',
|
|
38
|
+
'getInt',
|
|
39
|
+
'putInt',
|
|
40
|
+
'getLong',
|
|
41
|
+
'putLong',
|
|
42
|
+
'getString',
|
|
43
|
+
'putString',
|
|
44
|
+
'getItem',
|
|
45
|
+
'setItem',
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
const esMethods = new Set([
|
|
49
|
+
'getESDeviceInfo',
|
|
50
|
+
'getESSDKInfo',
|
|
51
|
+
'getESSDKVersionCode',
|
|
52
|
+
'getESSDKVersionName',
|
|
53
|
+
'getESPackageName',
|
|
54
|
+
'getESAppFilePath',
|
|
55
|
+
'getESAppRuntimePath',
|
|
56
|
+
'getSupportSchemes',
|
|
57
|
+
'launchESPageByArgs',
|
|
58
|
+
'launchESPage',
|
|
59
|
+
'launchNativeApp',
|
|
60
|
+
'launchNativeAppWithPackage',
|
|
61
|
+
'finish',
|
|
62
|
+
'finishAll',
|
|
63
|
+
'isModuleRegistered',
|
|
64
|
+
'isComponentRegistered',
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
// ESDevelop 模块方法
|
|
68
|
+
const developMethods = new Set([
|
|
69
|
+
'getDevelop',
|
|
70
|
+
'getPackageName',
|
|
71
|
+
'getVersionName',
|
|
72
|
+
'getVersionCode',
|
|
73
|
+
'getChannel',
|
|
74
|
+
'getVendor',
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
global.hippyCallNatives = (moduleName, methodName, callId, params) => {
|
|
78
|
+
console.log('hippyCallNatives', moduleName, methodName, callId, params)
|
|
79
|
+
|
|
80
|
+
// Handle ExtendModule.callUIFunction - support sid-based component lookup
|
|
81
|
+
if (moduleName === 'ExtendModule' && methodName === 'callUIFunction') {
|
|
82
|
+
// params format: [sid, funcName, args]
|
|
83
|
+
const [sid, funcName, args] = params || []
|
|
84
|
+
console.log('[Web Renderer] ExtendModule.callUIFunction:', { sid, funcName, args })
|
|
85
|
+
|
|
86
|
+
if (!sid || !funcName) {
|
|
87
|
+
console.warn('[Web Renderer] ExtendModule.callUIFunction missing sid or funcName')
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Find component by sid
|
|
92
|
+
const component = findComponentById(sid)
|
|
93
|
+
if (component && typeof component[funcName] === 'function') {
|
|
94
|
+
console.log('[Web Renderer] Calling', funcName, 'on component with sid:', sid)
|
|
95
|
+
try {
|
|
96
|
+
const result = component[funcName](...(args || []))
|
|
97
|
+
return result
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error('[Web Renderer] Error calling', funcName, ':', e)
|
|
100
|
+
}
|
|
101
|
+
} else if (component) {
|
|
102
|
+
console.warn('[Web Renderer] Component with sid:', sid, 'has no method:', funcName)
|
|
103
|
+
} else {
|
|
104
|
+
console.warn('[Web Renderer] Component not found for sid:', sid)
|
|
105
|
+
}
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle ESIJKAudioPlayerModule - redirect to web audio adapter
|
|
110
|
+
if (moduleName === 'ESIJKAudioPlayerModule') {
|
|
111
|
+
const audioModule = global.__WEB_MODULES__?.['ESIJKAudioPlayerModule']
|
|
112
|
+
if (audioModule && typeof audioModule[methodName] === 'function') {
|
|
113
|
+
console.log(
|
|
114
|
+
'[Web Renderer] ESIJKAudioPlayerModule (hippyCallNatives):',
|
|
115
|
+
methodName,
|
|
116
|
+
'callId:',
|
|
117
|
+
callId,
|
|
118
|
+
'params:',
|
|
119
|
+
params
|
|
120
|
+
)
|
|
121
|
+
// callId is the player id, params is array of additional args
|
|
122
|
+
const result = audioModule[methodName](callId, ...(params || [])).catch((e) => {
|
|
123
|
+
console.warn('[Web Renderer] ESIJKAudioPlayerModule error:', e)
|
|
124
|
+
})
|
|
125
|
+
return result
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const hasModule = !!engine?.modules?.[moduleName]
|
|
130
|
+
if (!hasModule) {
|
|
131
|
+
console.error(`[Web Renderer] Module ${moduleName} not found!`)
|
|
132
|
+
if (storageMethods.has(methodName)) {
|
|
133
|
+
return original('ESLocalStorageModule', methodName, callId, params)
|
|
134
|
+
}
|
|
135
|
+
if (esMethods.has(methodName)) {
|
|
136
|
+
return original('ESModule', methodName, callId, params)
|
|
137
|
+
}
|
|
138
|
+
if (developMethods.has(methodName)) {
|
|
139
|
+
return original('AndroidDevelopModule', methodName, callId, params)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return original(moduleName, methodName, callId, params)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
hippyCallNativesPatched = true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Patch Hippy.bridge.callNative to intercept and modify node creation
|
|
149
|
+
export function patchCallNative(engine) {
|
|
150
|
+
if (callNativePatched) {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const bridge = global.Hippy?.bridge
|
|
155
|
+
if (!bridge || !bridge.callNative) {
|
|
156
|
+
console.error('[Web Renderer] Hippy.bridge not available!')
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const originalCallNative = bridge.callNative.bind(bridge)
|
|
161
|
+
|
|
162
|
+
bridge.callNative = (moduleName, methodName, ...args) => {
|
|
163
|
+
// Handle ESIJKAudioPlayerModule - redirect to web audio adapter
|
|
164
|
+
if (moduleName === 'ESIJKAudioPlayerModule') {
|
|
165
|
+
const audioModule = global.__WEB_MODULES__?.['ESIJKAudioPlayerModule']
|
|
166
|
+
if (audioModule && typeof audioModule[methodName] === 'function') {
|
|
167
|
+
console.log('[Web Renderer] ESIJKAudioPlayerModule:', methodName, 'args:', args)
|
|
168
|
+
// Native call format: callNative(module, method, ...params)
|
|
169
|
+
// For play: callNative('ESIJKAudioPlayerModule', 'play', url)
|
|
170
|
+
// Module methods expect: method(id, ...params), but native calls don't have id
|
|
171
|
+
// So we pass -1 as default id
|
|
172
|
+
const id = -1
|
|
173
|
+
return audioModule[methodName](id, ...args)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (moduleName === 'UIManagerModule') {
|
|
178
|
+
// Handle addEventListener - register event listener on component
|
|
179
|
+
if (methodName === 'addEventListener') {
|
|
180
|
+
const [id, eventName, handler] = args
|
|
181
|
+
console.log('[Web Renderer] addEventListener:', { id, eventName })
|
|
182
|
+
|
|
183
|
+
const component = findComponentById(id)
|
|
184
|
+
if (component && component.addEventListener) {
|
|
185
|
+
component.addEventListener(eventName, handler)
|
|
186
|
+
console.log(
|
|
187
|
+
'[Web Renderer] Event listener registered:',
|
|
188
|
+
eventName,
|
|
189
|
+
'on component',
|
|
190
|
+
component.tagName
|
|
191
|
+
)
|
|
192
|
+
} else {
|
|
193
|
+
console.warn('[Web Renderer] Component not found or no addEventListener for id:', id)
|
|
194
|
+
}
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Handle removeEventListener
|
|
199
|
+
if (methodName === 'removeEventListener') {
|
|
200
|
+
const [id, eventName, handler] = args
|
|
201
|
+
console.log('[Web Renderer] removeEventListener:', { id, eventName })
|
|
202
|
+
|
|
203
|
+
const component = findComponentById(id)
|
|
204
|
+
if (component && component.removeEventListener) {
|
|
205
|
+
component.removeEventListener(eventName, handler)
|
|
206
|
+
}
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handle callUIFunction
|
|
211
|
+
if (methodName === 'callUIFunction') {
|
|
212
|
+
const [nodeId, funcName, params, callback] = args
|
|
213
|
+
console.log('[Web Renderer] callUIFunction:', { nodeId, funcName, params })
|
|
214
|
+
|
|
215
|
+
const component = findComponentById(nodeId)
|
|
216
|
+
if (component && typeof component[funcName] === 'function') {
|
|
217
|
+
console.log('[Web Renderer] Calling', funcName, 'on component', component.tagName)
|
|
218
|
+
try {
|
|
219
|
+
const result = component[funcName](...(params || []))
|
|
220
|
+
if (callback && typeof callback === 'function') {
|
|
221
|
+
callback(result)
|
|
222
|
+
}
|
|
223
|
+
return result
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.error('[Web Renderer] Error calling', funcName, ':', e)
|
|
226
|
+
}
|
|
227
|
+
} else if (component) {
|
|
228
|
+
console.warn('[Web Renderer] Component', component.tagName, 'has no method:', funcName)
|
|
229
|
+
} else {
|
|
230
|
+
console.warn('[Web Renderer] Component not found for id:', nodeId)
|
|
231
|
+
}
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (methodName === 'createNode') {
|
|
236
|
+
const [rootViewId, nodes] = args
|
|
237
|
+
|
|
238
|
+
if (Array.isArray(nodes)) {
|
|
239
|
+
const processedNodes = nodes.map((node) => {
|
|
240
|
+
let nodeData = node
|
|
241
|
+
let props = null
|
|
242
|
+
|
|
243
|
+
if (Array.isArray(node)) {
|
|
244
|
+
nodeData = node[0]
|
|
245
|
+
props = node[1]
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (nodeData && typeof nodeData === 'object') {
|
|
249
|
+
if (!nodeData.name) {
|
|
250
|
+
nodeData.name = 'View'
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Log component creation
|
|
254
|
+
console.log('[Web Renderer] Creating node:', nodeData.id, 'name:', nodeData.name)
|
|
255
|
+
|
|
256
|
+
if (!rootNodeId && (nodeData.pId === 'app' || nodeData.pId === rootViewId)) {
|
|
257
|
+
rootNodeId = nodeData.id
|
|
258
|
+
console.log('[Web Renderer] ROOT NODE detected: id=' + rootNodeId)
|
|
259
|
+
|
|
260
|
+
if (nodeData.props) {
|
|
261
|
+
nodeData.props.style = nodeData.props.style || {}
|
|
262
|
+
nodeData.props.style.width = TV_WIDTH
|
|
263
|
+
nodeData.props.style.height = TV_HEIGHT
|
|
264
|
+
delete nodeData.props.style.flex
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return Array.isArray(node) ? [nodeData, props] : nodeData
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
return originalCallNative(moduleName, methodName, rootViewId, processedNodes)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (methodName === 'updateNode') {
|
|
277
|
+
const [rootViewId, nodes] = args
|
|
278
|
+
|
|
279
|
+
if (Array.isArray(nodes)) {
|
|
280
|
+
const processedNodes = nodes.map((node) => {
|
|
281
|
+
let nodeData = node
|
|
282
|
+
let props = null
|
|
283
|
+
|
|
284
|
+
if (Array.isArray(node)) {
|
|
285
|
+
nodeData = node[0]
|
|
286
|
+
props = node[1]
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (nodeData && typeof nodeData === 'object') {
|
|
290
|
+
if (!nodeData.name) {
|
|
291
|
+
nodeData.name = 'View'
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (rootNodeId && nodeData.id === rootNodeId) {
|
|
295
|
+
if (nodeData.props) {
|
|
296
|
+
nodeData.props.style = nodeData.props.style || {}
|
|
297
|
+
nodeData.props.style.width = TV_WIDTH
|
|
298
|
+
nodeData.props.style.height = TV_HEIGHT
|
|
299
|
+
delete nodeData.props.style.flex
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return Array.isArray(node) ? [nodeData, props] : nodeData
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
return originalCallNative(moduleName, methodName, rootViewId, processedNodes)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Handle ESToastModule - redirect to web toast adapter
|
|
313
|
+
if (moduleName === 'ESToastModule') {
|
|
314
|
+
if (ESToastModule && typeof ESToastModule[methodName] === 'function') {
|
|
315
|
+
console.log('[Web Renderer] ESToastModule:', methodName, args)
|
|
316
|
+
ESToastModule[methodName](...args)
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Handle ESNetworkSpeedModule - redirect to web network speed adapter
|
|
322
|
+
if (moduleName === 'ESNetworkSpeedModule') {
|
|
323
|
+
if (ESNetworkSpeedModule && typeof ESNetworkSpeedModule[methodName] === 'function') {
|
|
324
|
+
console.log('[Web Renderer] ESNetworkSpeedModule:', methodName, args)
|
|
325
|
+
ESNetworkSpeedModule[methodName](...args)
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Handle ESPluginModule - intercept plugin installation for web platform
|
|
331
|
+
if (moduleName === 'ESPluginModule') {
|
|
332
|
+
console.log(`[Web Renderer] ESPluginModule call: ${methodName}`, args)
|
|
333
|
+
|
|
334
|
+
if (methodName === 'install') {
|
|
335
|
+
const pluginInfo = Array.isArray(args[0]) ? args[0][0] : args[0]
|
|
336
|
+
const pkg = pluginInfo?.pkg
|
|
337
|
+
console.log(`[Web Renderer] ESPluginModule.install: ${pkg}`, pluginInfo)
|
|
338
|
+
|
|
339
|
+
// 发送 EventBus 事件(格式必须匹配 ESPlugin 的期望)
|
|
340
|
+
try {
|
|
341
|
+
const { EventBus } = require('@extscreen/es3-vue')
|
|
342
|
+
if (EventBus?.$emit) {
|
|
343
|
+
EventBus.$emit('onESPluginStateChanged', {
|
|
344
|
+
success: true, // 必须是 success,不是 state
|
|
345
|
+
pkg,
|
|
346
|
+
status: 0,
|
|
347
|
+
msg: 'Plugin installed successfully (web)',
|
|
348
|
+
})
|
|
349
|
+
console.log(`[Web Renderer] EventBus event sent for ${pkg}`)
|
|
350
|
+
}
|
|
351
|
+
} catch (e) {
|
|
352
|
+
console.log('[Web Renderer] EventBus error:', e.message)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
console.log(`[Web Renderer] Plugin ${pkg} install completed (web)`)
|
|
356
|
+
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return originalCallNative(moduleName, methodName, ...args)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
callNativePatched = true
|
|
367
|
+
console.log('[Web Renderer] Patched Hippy.bridge.callNative')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Patch Hippy.bridge.callNativeWithPromise for ESIJKAudioPlayerModule and RuntimeDeviceModule
|
|
371
|
+
export function patchCallNativeWithPromise() {
|
|
372
|
+
const bridge = global.Hippy?.bridge
|
|
373
|
+
if (!bridge || !bridge.callNativeWithPromise) {
|
|
374
|
+
console.log('[Web Renderer] Hippy.bridge.callNativeWithPromise not available')
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const originalCallNativeWithPromise = bridge.callNativeWithPromise.bind(bridge)
|
|
379
|
+
|
|
380
|
+
bridge.callNativeWithPromise = (moduleName, methodName, ...args) => {
|
|
381
|
+
console.log('[Web Renderer] callNativeWithPromise:', moduleName, methodName, args)
|
|
382
|
+
// Handle ESIJKAudioPlayerModule - redirect to web audio adapter
|
|
383
|
+
if (moduleName === 'ESIJKAudioPlayerModule') {
|
|
384
|
+
const audioModule = global.__WEB_MODULES__?.['ESIJKAudioPlayerModule']
|
|
385
|
+
if (audioModule && typeof audioModule[methodName] === 'function') {
|
|
386
|
+
console.log('[Web Renderer] ESIJKAudioPlayerModule (Promise):', methodName, args)
|
|
387
|
+
// Native call format doesn't have id, pass -1 as default
|
|
388
|
+
const id = -1
|
|
389
|
+
const result = audioModule[methodName](id, ...args)
|
|
390
|
+
if (result instanceof Promise) {
|
|
391
|
+
return result
|
|
392
|
+
}
|
|
393
|
+
return Promise.resolve(result)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Handle RuntimeDeviceModule - redirect to web device adapter
|
|
398
|
+
if (moduleName === 'RuntimeDeviceModule') {
|
|
399
|
+
if (RuntimeDeviceModule && typeof RuntimeDeviceModule[methodName] === 'function') {
|
|
400
|
+
console.log('[Web Renderer] RuntimeDeviceModule (Promise):', methodName, args)
|
|
401
|
+
const result = RuntimeDeviceModule[methodName](...args)
|
|
402
|
+
if (result instanceof Promise) {
|
|
403
|
+
return result
|
|
404
|
+
}
|
|
405
|
+
return Promise.resolve(result)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Handle AndroidDeviceModule - redirect to web device adapter
|
|
410
|
+
if (moduleName === 'AndroidDeviceModule') {
|
|
411
|
+
if (AndroidDeviceModule && typeof AndroidDeviceModule[methodName] === 'function') {
|
|
412
|
+
console.log('[Web Renderer] AndroidDeviceModule (Promise):', methodName, args)
|
|
413
|
+
const result = AndroidDeviceModule[methodName](...args)
|
|
414
|
+
if (result instanceof Promise) {
|
|
415
|
+
return result
|
|
416
|
+
}
|
|
417
|
+
return Promise.resolve(result)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Handle AndroidNetworkModule - redirect to web network adapter
|
|
422
|
+
if (moduleName === 'AndroidNetworkModule') {
|
|
423
|
+
if (AndroidNetworkModule && typeof AndroidNetworkModule[methodName] === 'function') {
|
|
424
|
+
console.log('[Web Renderer] AndroidNetworkModule (Promise):', methodName, args)
|
|
425
|
+
const result = AndroidNetworkModule[methodName](...args)
|
|
426
|
+
if (result instanceof Promise) {
|
|
427
|
+
return result
|
|
428
|
+
}
|
|
429
|
+
return Promise.resolve(result)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return originalCallNativeWithPromise(moduleName, methodName, ...args)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
console.log('[Web Renderer] Patched Hippy.bridge.callNativeWithPromise')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Patch Hippy.document.callUIFunction
|
|
440
|
+
export function patchDocumentCallUIFunction() {
|
|
441
|
+
const originalDocCallUIFunction = global.Hippy.document.callUIFunction.bind(global.Hippy.document)
|
|
442
|
+
|
|
443
|
+
// Retry helper for callUIFunction - handles timing issues where component not yet created
|
|
444
|
+
const retryCallUIFunction = (
|
|
445
|
+
nodeId,
|
|
446
|
+
funcName,
|
|
447
|
+
params,
|
|
448
|
+
callback,
|
|
449
|
+
retriesLeft = 10,
|
|
450
|
+
delay = 50
|
|
451
|
+
) => {
|
|
452
|
+
const component = findComponentById(nodeId)
|
|
453
|
+
if (component && typeof component[funcName] === 'function') {
|
|
454
|
+
console.log(
|
|
455
|
+
'[Web Renderer] Retry successful - Calling',
|
|
456
|
+
funcName,
|
|
457
|
+
'on component',
|
|
458
|
+
component.tagName
|
|
459
|
+
)
|
|
460
|
+
try {
|
|
461
|
+
const result = component[funcName](params)
|
|
462
|
+
if (callback && typeof callback === 'function') {
|
|
463
|
+
callback(result)
|
|
464
|
+
}
|
|
465
|
+
return result
|
|
466
|
+
} catch (e) {
|
|
467
|
+
console.error('[Web Renderer] Error calling', funcName, ':', e)
|
|
468
|
+
}
|
|
469
|
+
} else if (retriesLeft > 0) {
|
|
470
|
+
console.log(
|
|
471
|
+
'[Web Renderer] Component not found for id:',
|
|
472
|
+
nodeId,
|
|
473
|
+
'- retrying in',
|
|
474
|
+
delay,
|
|
475
|
+
'ms (retries left:',
|
|
476
|
+
retriesLeft,
|
|
477
|
+
')'
|
|
478
|
+
)
|
|
479
|
+
setTimeout(() => {
|
|
480
|
+
retryCallUIFunction(nodeId, funcName, params, callback, retriesLeft - 1, delay)
|
|
481
|
+
}, delay)
|
|
482
|
+
} else {
|
|
483
|
+
console.warn('[Web Renderer] Retry exhausted - Component still not found for id:', nodeId)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
global.Hippy.document.callUIFunction = (nodeId, funcName, params, callback) => {
|
|
488
|
+
console.log('[Web Renderer] Hippy.document.callUIFunction:', {
|
|
489
|
+
nodeId,
|
|
490
|
+
funcName,
|
|
491
|
+
paramsLength: params?.length,
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
const component = findComponentById(nodeId)
|
|
495
|
+
if (component && typeof component[funcName] === 'function') {
|
|
496
|
+
console.log(
|
|
497
|
+
'[Web Renderer] Calling',
|
|
498
|
+
funcName,
|
|
499
|
+
'on component',
|
|
500
|
+
component.tagName,
|
|
501
|
+
'with',
|
|
502
|
+
params?.length,
|
|
503
|
+
'params'
|
|
504
|
+
)
|
|
505
|
+
try {
|
|
506
|
+
const result = component[funcName](params)
|
|
507
|
+
if (callback && typeof callback === 'function') {
|
|
508
|
+
callback(result)
|
|
509
|
+
}
|
|
510
|
+
return result
|
|
511
|
+
} catch (e) {
|
|
512
|
+
console.error('[Web Renderer] Error calling', funcName, ':', e)
|
|
513
|
+
}
|
|
514
|
+
} else if (component) {
|
|
515
|
+
console.warn('[Web Renderer] Component', component.tagName, 'has no method:', funcName)
|
|
516
|
+
} else {
|
|
517
|
+
// Component not found yet - use retry mechanism
|
|
518
|
+
retryCallUIFunction(nodeId, funcName, params, callback)
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return originalDocCallUIFunction(nodeId, funcName, params, callback)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
console.log('[Web Renderer] Patched Hippy.document.callUIFunction')
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Override Hippy.device dimensions
|
|
529
|
+
export function overrideDeviceDimensions() {
|
|
530
|
+
if (typeof global.Hippy === 'undefined' || !global.Hippy.device) {
|
|
531
|
+
setTimeout(overrideDeviceDimensions, 10)
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
global.Hippy.device.window = {
|
|
536
|
+
width: TV_WIDTH,
|
|
537
|
+
height: TV_HEIGHT,
|
|
538
|
+
scale: 1,
|
|
539
|
+
fontScale: 1,
|
|
540
|
+
statusBarHeight: 0,
|
|
541
|
+
navigatorBarHeight: 0,
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
global.Hippy.device.screen = {
|
|
545
|
+
width: TV_WIDTH,
|
|
546
|
+
height: TV_HEIGHT,
|
|
547
|
+
scale: 1,
|
|
548
|
+
fontScale: 1,
|
|
549
|
+
statusBarHeight: 0,
|
|
550
|
+
navigatorBarHeight: 0,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
global.Hippy.device.pixelRatio = 1
|
|
554
|
+
|
|
555
|
+
console.log('[Web Renderer] Overridden Hippy.device dimensions to', TV_WIDTH, 'x', TV_HEIGHT)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Fix #app container style
|
|
559
|
+
export function fixAppContainerStyle() {
|
|
560
|
+
const appContainer = document.getElementById('app')
|
|
561
|
+
if (!appContainer) {
|
|
562
|
+
setTimeout(fixAppContainerStyle, 10)
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
appContainer.style.width = TV_WIDTH + 'px'
|
|
567
|
+
appContainer.style.height = TV_HEIGHT + 'px'
|
|
568
|
+
|
|
569
|
+
console.log('[Web Renderer] Fixed #app container style to', TV_WIDTH, 'x', TV_HEIGHT)
|
|
570
|
+
|
|
571
|
+
const observer = new MutationObserver(() => {
|
|
572
|
+
if (
|
|
573
|
+
appContainer.style.width !== TV_WIDTH + 'px' ||
|
|
574
|
+
appContainer.style.height !== TV_HEIGHT + 'px'
|
|
575
|
+
) {
|
|
576
|
+
appContainer.style.width = TV_WIDTH + 'px'
|
|
577
|
+
appContainer.style.height = TV_HEIGHT + 'px'
|
|
578
|
+
}
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
observer.observe(appContainer, {
|
|
582
|
+
attributes: true,
|
|
583
|
+
attributeFilter: ['style'],
|
|
584
|
+
})
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Focus style property names
|
|
588
|
+
const FOCUS_STYLE_PROPS = [
|
|
589
|
+
'focusColor',
|
|
590
|
+
'focusBackgroundColor',
|
|
591
|
+
'focusTextColor',
|
|
592
|
+
'focusBorderColor',
|
|
593
|
+
'focusBorderWidth',
|
|
594
|
+
'focusBorderRadius',
|
|
595
|
+
'focusOpacity',
|
|
596
|
+
'focusScale',
|
|
597
|
+
]
|
|
598
|
+
|
|
599
|
+
// Properties that are numeric values, not colors (should not be converted via argbToCssColor)
|
|
600
|
+
const NUMERIC_FOCUS_PROPS = ['focusBorderWidth', 'focusBorderRadius', 'focusOpacity', 'focusScale']
|
|
601
|
+
|
|
602
|
+
// Convert ARGB number to CSS color string
|
|
603
|
+
function argbToCssColor(argb) {
|
|
604
|
+
if (typeof argb !== 'number') return argb
|
|
605
|
+
const a = ((argb >> 24) & 0xff) / 255
|
|
606
|
+
const r = (argb >> 16) & 0xff
|
|
607
|
+
const g = (argb >> 8) & 0xff
|
|
608
|
+
const b = argb & 0xff
|
|
609
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Convert Android ARGB color string (#AARRGGBB) to CSS color (#RRGGBBAA or rgba())
|
|
613
|
+
function convertArgbColorString(color) {
|
|
614
|
+
if (typeof color !== 'string') return color
|
|
615
|
+
|
|
616
|
+
// Handle #AARRGGBB format (8 hex chars with #)
|
|
617
|
+
if (/^#[0-9A-Fa-f]{8}$/.test(color)) {
|
|
618
|
+
const alpha = color.substring(1, 3)
|
|
619
|
+
const red = color.substring(3, 5)
|
|
620
|
+
const green = color.substring(5, 7)
|
|
621
|
+
const blue = color.substring(7, 9)
|
|
622
|
+
|
|
623
|
+
// Convert hex to decimal
|
|
624
|
+
const a = parseInt(alpha, 16) / 255
|
|
625
|
+
const r = parseInt(red, 16)
|
|
626
|
+
const g = parseInt(green, 16)
|
|
627
|
+
const b = parseInt(blue, 16)
|
|
628
|
+
|
|
629
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Handle #ARGB format (4 hex chars with #)
|
|
633
|
+
if (/^#[0-9A-Fa-f]{4}$/.test(color)) {
|
|
634
|
+
const alpha = color.substring(1, 2) + color.substring(1, 2)
|
|
635
|
+
const red = color.substring(2, 3) + color.substring(2, 3)
|
|
636
|
+
const green = color.substring(3, 4) + color.substring(3, 4)
|
|
637
|
+
const blue = color.substring(4, 5) + color.substring(4, 5)
|
|
638
|
+
|
|
639
|
+
const a = parseInt(alpha, 16) / 255
|
|
640
|
+
const r = parseInt(red, 16)
|
|
641
|
+
const g = parseInt(green, 16)
|
|
642
|
+
const b = parseInt(blue, 16)
|
|
643
|
+
|
|
644
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Already in CSS format (#RRGGBB, #RGB, rgba(), etc.)
|
|
648
|
+
return color
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Gradient orientation mapping (ESGradientOrientation to CSS direction)
|
|
652
|
+
const GRADIENT_ORIENTATION_MAP = {
|
|
653
|
+
0: 'to bottom', // TOP_BOTTOM
|
|
654
|
+
1: 'to bottom left', // TR_BL
|
|
655
|
+
2: 'to left', // RIGHT_LEFT
|
|
656
|
+
3: 'to top left', // BR_TL
|
|
657
|
+
4: 'to top', // BOTTOM_TOP
|
|
658
|
+
5: 'to top right', // BL_TR
|
|
659
|
+
6: 'to right', // LEFT_RIGHT
|
|
660
|
+
7: 'to bottom right', // TL_BR
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Convert QTGradient to CSS gradient string
|
|
664
|
+
function gradientBackgroundToCss(gradient) {
|
|
665
|
+
if (!gradient || !gradient.colors || gradient.colors.length === 0) {
|
|
666
|
+
return null
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Convert colors from ARGB format to CSS format
|
|
670
|
+
const colors = gradient.colors.map((color) => convertArgbColorString(color))
|
|
671
|
+
const orientation = gradient.orientation ?? 6 // Default: LEFT_RIGHT
|
|
672
|
+
const type = gradient.type ?? 0 // Default: LINEAR
|
|
673
|
+
const cornerRadius = gradient.cornerRadius
|
|
674
|
+
const cornerRadii4 = gradient.cornerRadii4
|
|
675
|
+
const cornerRadii8 = gradient.cornerRadii8
|
|
676
|
+
|
|
677
|
+
// Build CSS gradient
|
|
678
|
+
let cssGradient = ''
|
|
679
|
+
|
|
680
|
+
if (type === 1) {
|
|
681
|
+
// Radial gradient
|
|
682
|
+
const gradientRadius = gradient.gradientRadius || 'farthest-corner'
|
|
683
|
+
cssGradient = `radial-gradient(circle ${gradientRadius}, ${colors.join(', ')})`
|
|
684
|
+
} else {
|
|
685
|
+
// Linear gradient (default)
|
|
686
|
+
const direction = GRADIENT_ORIENTATION_MAP[orientation] || 'to right'
|
|
687
|
+
cssGradient = `linear-gradient(${direction}, ${colors.join(', ')})`
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Build border-radius
|
|
691
|
+
let borderRadius = ''
|
|
692
|
+
if (cornerRadii8 && cornerRadii8.length === 8) {
|
|
693
|
+
// 8 values: [topLeftX, topLeftY, topRightX, topRightY, bottomRightX, bottomRightY, bottomLeftX, bottomLeftY]
|
|
694
|
+
borderRadius = `${cornerRadii8[0]}px ${cornerRadii8[2]}px ${cornerRadii8[4]}px ${cornerRadii8[6]}px / ${cornerRadii8[1]}px ${cornerRadii8[3]}px ${cornerRadii8[5]}px ${cornerRadii8[7]}px`
|
|
695
|
+
} else if (cornerRadii4 && cornerRadii4.length === 4) {
|
|
696
|
+
// 4 values: [topLeft, topRight, bottomRight, bottomLeft]
|
|
697
|
+
borderRadius = `${cornerRadii4[0]}px ${cornerRadii4[1]}px ${cornerRadii4[2]}px ${cornerRadii4[3]}px`
|
|
698
|
+
} else if (cornerRadius !== undefined && cornerRadius !== null) {
|
|
699
|
+
borderRadius = `${cornerRadius}px`
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
background: cssGradient,
|
|
704
|
+
borderRadius: borderRadius,
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Extract focus styles from style object and store as data attributes
|
|
709
|
+
function extractFocusStyles(element, styleObj) {
|
|
710
|
+
if (!styleObj || typeof styleObj !== 'object') return styleObj
|
|
711
|
+
if (!element || !element.setAttribute) return styleObj
|
|
712
|
+
|
|
713
|
+
const result = { ...styleObj }
|
|
714
|
+
|
|
715
|
+
// Handle gradientBackground - convert to CSS gradient
|
|
716
|
+
const gradientBg = styleObj.gradientBackground
|
|
717
|
+
if (gradientBg) {
|
|
718
|
+
const cssGradient = gradientBackgroundToCss(gradientBg)
|
|
719
|
+
if (cssGradient) {
|
|
720
|
+
result.background = cssGradient.background
|
|
721
|
+
if (cssGradient.borderRadius) {
|
|
722
|
+
result.borderRadius = cssGradient.borderRadius
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
delete result.gradientBackground
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
FOCUS_STYLE_PROPS.forEach((prop) => {
|
|
729
|
+
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
730
|
+
const value = styleObj[prop] || styleObj[kebabProp]
|
|
731
|
+
|
|
732
|
+
if (value !== undefined) {
|
|
733
|
+
// Numeric props should not be converted as colors
|
|
734
|
+
let cssValue
|
|
735
|
+
if (NUMERIC_FOCUS_PROPS.includes(prop)) {
|
|
736
|
+
cssValue = value
|
|
737
|
+
} else {
|
|
738
|
+
// Convert ARGB to CSS color if needed (for color props)
|
|
739
|
+
cssValue = typeof value === 'number' ? argbToCssColor(value) : value
|
|
740
|
+
}
|
|
741
|
+
// Store as data attribute
|
|
742
|
+
element.setAttribute('data-' + kebabProp, String(cssValue))
|
|
743
|
+
// Remove from style object (browser won't understand it)
|
|
744
|
+
delete result[prop]
|
|
745
|
+
delete result[kebabProp]
|
|
746
|
+
}
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
return result
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Patch HippyWeb.setElementStyle to extract focus styles
|
|
753
|
+
function patchHippyWebSetElementStyle(engine) {
|
|
754
|
+
const HippyWeb = engine.context?.getModuleByName?.('HippyWeb')
|
|
755
|
+
if (HippyWeb && HippyWeb.setElementStyle && !HippyWeb._setElementStylePatched) {
|
|
756
|
+
const originalSetElementStyle = HippyWeb.setElementStyle.bind(HippyWeb)
|
|
757
|
+
HippyWeb.setElementStyle = (element, styleObj) => {
|
|
758
|
+
const cleanedStyle = extractFocusStyles(element, styleObj)
|
|
759
|
+
originalSetElementStyle(element, cleanedStyle)
|
|
760
|
+
}
|
|
761
|
+
HippyWeb._setElementStylePatched = true
|
|
762
|
+
console.log('[Web Renderer] Patched HippyWeb.setElementStyle for focus styles')
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function patchFocusCssVars() {
|
|
767
|
+
const focusKebabProps = [
|
|
768
|
+
'focus-color',
|
|
769
|
+
'focus-background-color',
|
|
770
|
+
'focus-background',
|
|
771
|
+
'focus-text-color',
|
|
772
|
+
'focus-border-color',
|
|
773
|
+
'focus-border-width',
|
|
774
|
+
'focus-border-radius',
|
|
775
|
+
'focus-opacity',
|
|
776
|
+
'focus-scale',
|
|
777
|
+
]
|
|
778
|
+
|
|
779
|
+
const patchRuleStyle = (style) => {
|
|
780
|
+
if (!style) return
|
|
781
|
+
focusKebabProps.forEach((prop) => {
|
|
782
|
+
const value = style.getPropertyValue(prop)
|
|
783
|
+
if (!value) return
|
|
784
|
+
const varName = '--' + prop
|
|
785
|
+
const existing = style.getPropertyValue(varName)
|
|
786
|
+
if (existing) return
|
|
787
|
+
style.setProperty(varName, value.trim())
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const visitRules = (rules) => {
|
|
792
|
+
if (!rules) return
|
|
793
|
+
Array.from(rules).forEach((rule) => {
|
|
794
|
+
if (!rule) return
|
|
795
|
+
const type = rule.type
|
|
796
|
+
if (type === CSSRule.STYLE_RULE) {
|
|
797
|
+
patchRuleStyle(rule.style)
|
|
798
|
+
} else if (
|
|
799
|
+
type === CSSRule.MEDIA_RULE ||
|
|
800
|
+
type === CSSRule.SUPPORTS_RULE ||
|
|
801
|
+
type === CSSRule.DOCUMENT_RULE
|
|
802
|
+
) {
|
|
803
|
+
visitRules(rule.cssRules)
|
|
804
|
+
}
|
|
805
|
+
})
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
Array.from(document.styleSheets || []).forEach((sheet) => {
|
|
809
|
+
try {
|
|
810
|
+
visitRules(sheet.cssRules)
|
|
811
|
+
} catch (e) {}
|
|
812
|
+
})
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
let _focusCssVarObserver = null
|
|
816
|
+
function ensureFocusCssVarsPatched() {
|
|
817
|
+
patchFocusCssVars()
|
|
818
|
+
if (_focusCssVarObserver) return
|
|
819
|
+
_focusCssVarObserver = new MutationObserver(() => patchFocusCssVars())
|
|
820
|
+
_focusCssVarObserver.observe(document.head, { childList: true, subtree: true })
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Patch UIManagerModule
|
|
824
|
+
export function patchUIManager(engine) {
|
|
825
|
+
const uiManager = engine.context?.getModuleByName?.('UIManagerModule')
|
|
826
|
+
if (uiManager && !uiManager._addEventListenerPatched) {
|
|
827
|
+
const originalAddEventListener = uiManager.addEventListener.bind(uiManager)
|
|
828
|
+
uiManager.addEventListener = (id, eventName, callback) => {
|
|
829
|
+
const view = uiManager.findViewById(id)
|
|
830
|
+
if (view && view.addEventListener) {
|
|
831
|
+
view.addEventListener(eventName, callback)
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const originalDefaultUpdateViewProps = uiManager.defaultUpdateViewProps.bind(uiManager)
|
|
836
|
+
uiManager.defaultUpdateViewProps = (view, props) => {
|
|
837
|
+
if (!view.dom) {
|
|
838
|
+
console.warn('[UIManagerModule] defaultUpdateViewProps: view.dom is null, skipping')
|
|
839
|
+
return
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (props?.style) {
|
|
843
|
+
// If this is ESPageRootView or ESPageRouterView, intercept and force default background
|
|
844
|
+
if (
|
|
845
|
+
(view._originalComponentName === 'ESPageRootView' ||
|
|
846
|
+
view._originalComponentName === 'ESPageRouterView') &&
|
|
847
|
+
(!props.style.backgroundColor || props.style.backgroundColor === 'transparent') &&
|
|
848
|
+
!props.isDialogMode
|
|
849
|
+
) {
|
|
850
|
+
props.style.backgroundColor = '#000000'
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
props.style = extractFocusStyles(view.dom, props.style)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const normalizeClass = (value) => {
|
|
857
|
+
if (!value) return ''
|
|
858
|
+
if (typeof value === 'string') return value
|
|
859
|
+
if (Array.isArray(value)) return value.map(normalizeClass).filter(Boolean).join(' ')
|
|
860
|
+
if (typeof value === 'object')
|
|
861
|
+
return Object.keys(value)
|
|
862
|
+
.filter((k) => value[k])
|
|
863
|
+
.join(' ')
|
|
864
|
+
return String(value)
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const applyClass = (element, value) => {
|
|
868
|
+
const cls = normalizeClass(value)
|
|
869
|
+
if (!cls) return
|
|
870
|
+
cls
|
|
871
|
+
.split(/\s+/)
|
|
872
|
+
.filter(Boolean)
|
|
873
|
+
.forEach((c) => element.classList.add(c))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const applyFocusProps = (element, p) => {
|
|
877
|
+
if (!element || !element.setAttribute || !p) return
|
|
878
|
+
FOCUS_STYLE_PROPS.forEach((prop) => {
|
|
879
|
+
const kebabProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
880
|
+
const value = p[prop] ?? p[kebabProp]
|
|
881
|
+
if (value === undefined || value === null) return
|
|
882
|
+
// Numeric props should not be converted as colors
|
|
883
|
+
const cssValue = NUMERIC_FOCUS_PROPS.includes(prop)
|
|
884
|
+
? value
|
|
885
|
+
: typeof value === 'number'
|
|
886
|
+
? argbToCssColor(value)
|
|
887
|
+
: value
|
|
888
|
+
element.setAttribute('data-' + kebabProp, String(cssValue))
|
|
889
|
+
})
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
applyClass(view.dom, props?.className || props?.class)
|
|
893
|
+
applyFocusProps(view.dom, props)
|
|
894
|
+
const parent = uiManager.findViewById(view.pId)
|
|
895
|
+
if (!parent || !parent.dom) {
|
|
896
|
+
console.log(
|
|
897
|
+
'[UIManagerModule] defaultUpdateViewProps: parent or parent.dom is null, using safe update'
|
|
898
|
+
)
|
|
899
|
+
const keys = Object.keys(props)
|
|
900
|
+
if (props.style) {
|
|
901
|
+
const HippyWeb = engine.context.getModuleByName('HippyWeb')
|
|
902
|
+
if (HippyWeb?.setElementStyle) {
|
|
903
|
+
HippyWeb.setElementStyle(view.dom, props.style)
|
|
904
|
+
} else {
|
|
905
|
+
Object.assign(view.dom.style, props.style)
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return
|
|
909
|
+
}
|
|
910
|
+
originalDefaultUpdateViewProps(view, props)
|
|
911
|
+
applyClass(view.dom, props?.className || props?.class)
|
|
912
|
+
applyFocusProps(view.dom, props)
|
|
913
|
+
|
|
914
|
+
// Apply gradientBackground from props (not in style)
|
|
915
|
+
if (props?.gradientBackground) {
|
|
916
|
+
const cssGradient = gradientBackgroundToCss(props.gradientBackground)
|
|
917
|
+
if (cssGradient) {
|
|
918
|
+
view.dom.style.background = cssGradient.background
|
|
919
|
+
if (cssGradient.borderRadius) {
|
|
920
|
+
view.dom.style.borderRadius = cssGradient.borderRadius
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
uiManager._addEventListenerPatched = true
|
|
927
|
+
console.log('[Web Renderer] Patched UIManagerModule methods for debugging and safety')
|
|
928
|
+
return true
|
|
929
|
+
}
|
|
930
|
+
return false
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Apply all patches
|
|
934
|
+
export function applyAllPatches(engine) {
|
|
935
|
+
setWebEngine(engine)
|
|
936
|
+
patchHippyCallNatives(engine)
|
|
937
|
+
patchCallNative(engine)
|
|
938
|
+
patchCallNativeWithPromise()
|
|
939
|
+
patchDocumentCallUIFunction()
|
|
940
|
+
overrideDeviceDimensions()
|
|
941
|
+
fixAppContainerStyle()
|
|
942
|
+
|
|
943
|
+
// Patch HippyWeb.setElementStyle for focus styles
|
|
944
|
+
patchHippyWebSetElementStyle(engine)
|
|
945
|
+
ensureFocusCssVarsPatched()
|
|
946
|
+
|
|
947
|
+
// Try to patch UIManagerModule
|
|
948
|
+
if (!patchUIManager(engine)) {
|
|
949
|
+
const tryPatch = () => {
|
|
950
|
+
if (patchUIManager(engine)) {
|
|
951
|
+
console.log('[Web Renderer] UIManagerModule patched via requestAnimationFrame')
|
|
952
|
+
} else {
|
|
953
|
+
requestAnimationFrame(tryPatch)
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
requestAnimationFrame(tryPatch)
|
|
957
|
+
}
|
|
958
|
+
}
|