@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.
Files changed (56) hide show
  1. package/package.json +24 -0
  2. package/src/adapters/es3-video-player.js +828 -0
  3. package/src/components/Modal.js +119 -0
  4. package/src/components/QtAnimationView.js +678 -0
  5. package/src/components/QtBaseComponent.js +165 -0
  6. package/src/components/QtFastListView.js +1920 -0
  7. package/src/components/QtFlexView.js +799 -0
  8. package/src/components/QtImage.js +203 -0
  9. package/src/components/QtItemFrame.js +239 -0
  10. package/src/components/QtItemStoreView.js +93 -0
  11. package/src/components/QtItemView.js +125 -0
  12. package/src/components/QtListView.js +331 -0
  13. package/src/components/QtLoadingView.js +55 -0
  14. package/src/components/QtPageRootView.js +19 -0
  15. package/src/components/QtPlayMark.js +168 -0
  16. package/src/components/QtProgressBar.js +199 -0
  17. package/src/components/QtQRCode.js +78 -0
  18. package/src/components/QtReplaceChild.js +149 -0
  19. package/src/components/QtRippleView.js +166 -0
  20. package/src/components/QtSeekBar.js +409 -0
  21. package/src/components/QtText.js +679 -0
  22. package/src/components/QtTransitionImage.js +170 -0
  23. package/src/components/QtView.js +706 -0
  24. package/src/components/QtWebView.js +613 -0
  25. package/src/components/TabsView.js +420 -0
  26. package/src/components/ViewPager.js +206 -0
  27. package/src/components/index.js +24 -0
  28. package/src/components/plugins/TextV2Component.js +70 -0
  29. package/src/components/plugins/index.js +7 -0
  30. package/src/core/SceneBuilder.js +58 -0
  31. package/src/core/TVFocusManager.js +2014 -0
  32. package/src/core/asyncLocalStorage.js +175 -0
  33. package/src/core/autoProxy.js +165 -0
  34. package/src/core/componentRegistry.js +84 -0
  35. package/src/core/constants.js +6 -0
  36. package/src/core/index.js +8 -0
  37. package/src/core/moduleUtils.js +36 -0
  38. package/src/core/patches.js +958 -0
  39. package/src/core/templateBinding.js +666 -0
  40. package/src/index.js +246 -0
  41. package/src/modules/AndroidDevelopModule.js +101 -0
  42. package/src/modules/AndroidDeviceModule.js +341 -0
  43. package/src/modules/AndroidNetworkModule.js +178 -0
  44. package/src/modules/AndroidSharedPreferencesModule.js +100 -0
  45. package/src/modules/ESDeviceInfoModule.js +450 -0
  46. package/src/modules/ESGroupDataModule.js +195 -0
  47. package/src/modules/ESIJKAudioPlayerModule.js +477 -0
  48. package/src/modules/ESLocalStorageModule.js +100 -0
  49. package/src/modules/ESLogModule.js +65 -0
  50. package/src/modules/ESModule.js +106 -0
  51. package/src/modules/ESNetworkSpeedModule.js +117 -0
  52. package/src/modules/ESToastModule.js +172 -0
  53. package/src/modules/EsNativeModule.js +117 -0
  54. package/src/modules/FastListModule.js +101 -0
  55. package/src/modules/FocusModule.js +145 -0
  56. package/src/modules/RuntimeDeviceModule.js +176 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * 异步 localStorage 适配层
3
+ *
4
+ * 将浏览器同步的 localStorage API 包装为异步 API
5
+ * 以保持与 Android 平台的一致性
6
+ *
7
+ * 用法:
8
+ * localStorage.getItem(key).then(value => { ... })
9
+ * localStorage.setItem(key, value).then(() => { ... })
10
+ */
11
+
12
+ const rawLocalStorage = window.localStorage
13
+
14
+ // 存储变更监听器
15
+ const changeListeners = new Map()
16
+
17
+ /**
18
+ * 异步 localStorage 适配器
19
+ * 包装浏览器原生 localStorage,提供异步 API
20
+ */
21
+ export const asyncLocalStorage = {
22
+ /**
23
+ * 异步获取存储项
24
+ * @param {string} key - 键名
25
+ * @returns {Promise<string|null>} - 返回 Promise
26
+ */
27
+ getItem(key) {
28
+ return new Promise((resolve) => {
29
+ // 使用 setTimeout 模拟异步行为
30
+ setTimeout(() => {
31
+ try {
32
+ const value = rawLocalStorage.getItem(key)
33
+ resolve(value)
34
+ } catch (e) {
35
+ console.warn('[AsyncLocalStorage] getItem error:', e)
36
+ resolve(null)
37
+ }
38
+ }, 0)
39
+ })
40
+ },
41
+
42
+ /**
43
+ * 异步设置存储项
44
+ * @param {string} key - 键名
45
+ * @param {string} value - 值
46
+ * @returns {Promise<void>}
47
+ */
48
+ setItem(key, value) {
49
+ return new Promise((resolve, reject) => {
50
+ setTimeout(() => {
51
+ try {
52
+ const oldValue = rawLocalStorage.getItem(key)
53
+ rawLocalStorage.setItem(key, value)
54
+ // 触发变更事件
55
+ _notifyChange(key, oldValue, value)
56
+ resolve()
57
+ } catch (e) {
58
+ console.warn('[AsyncLocalStorage] setItem error:', e)
59
+ reject(e)
60
+ }
61
+ }, 0)
62
+ })
63
+ },
64
+
65
+ /**
66
+ * 异步删除存储项
67
+ * @param {string} key - 键名
68
+ * @returns {Promise<void>}
69
+ */
70
+ removeItem(key) {
71
+ return new Promise((resolve) => {
72
+ setTimeout(() => {
73
+ try {
74
+ const oldValue = rawLocalStorage.getItem(key)
75
+ rawLocalStorage.removeItem(key)
76
+ _notifyChange(key, oldValue, null)
77
+ resolve()
78
+ } catch (e) {
79
+ console.warn('[AsyncLocalStorage] removeItem error:', e)
80
+ resolve()
81
+ }
82
+ }, 0)
83
+ })
84
+ },
85
+
86
+ /**
87
+ * 异步清空存储
88
+ * @returns {Promise<void>}
89
+ */
90
+ clear() {
91
+ return new Promise((resolve) => {
92
+ setTimeout(() => {
93
+ try {
94
+ rawLocalStorage.clear()
95
+ resolve()
96
+ } catch (e) {
97
+ console.warn('[AsyncLocalStorage] clear error:', e)
98
+ resolve()
99
+ }
100
+ }, 0)
101
+ })
102
+ },
103
+
104
+ /**
105
+ * 获取所有键名
106
+ * @param {number} index - 索引
107
+ * @returns {Promise<string|null>}
108
+ */
109
+ key(index) {
110
+ return new Promise((resolve) => {
111
+ setTimeout(() => {
112
+ try {
113
+ resolve(rawLocalStorage.key(index))
114
+ } catch (e) {
115
+ console.warn('[AsyncLocalStorage] key error:', e)
116
+ resolve(null)
117
+ }
118
+ }, 0)
119
+ })
120
+ },
121
+
122
+ /**
123
+ * 同步获取存储长度(保持同步,因为通常不需要异步)
124
+ */
125
+ get length() {
126
+ return rawLocalStorage.length
127
+ },
128
+
129
+ /**
130
+ * 监听存储变更
131
+ * @param {function} callback - 回调函数 (key, newValue, oldValue) => void
132
+ * @returns {function} 取消监听函数
133
+ */
134
+ addChangeListener(callback) {
135
+ const id = Date.now() + Math.random()
136
+ changeListeners.set(id, callback)
137
+ return () => changeListeners.delete(id)
138
+ },
139
+ }
140
+
141
+ /**
142
+ * 通知存储变更
143
+ */
144
+ function _notifyChange(key, oldValue, newValue) {
145
+ changeListeners.forEach((callback) => {
146
+ try {
147
+ callback(key, newValue, oldValue)
148
+ } catch (e) {
149
+ console.warn('[AsyncLocalStorage] listener error:', e)
150
+ }
151
+ })
152
+ }
153
+
154
+ /**
155
+ * 初始化异步 localStorage
156
+ * 替换全局 localStorage 为异步版本
157
+ */
158
+ export function initAsyncLocalStorage() {
159
+ // 保存原生 localStorage 引用
160
+ globalThis.__nativeLocalStorage = rawLocalStorage
161
+
162
+ // 替换为异步版本
163
+ globalThis.__localStorage = asyncLocalStorage
164
+
165
+ // 同时替换 window.localStorage(某些代码可能直接使用 window.localStorage)
166
+ Object.defineProperty(window, 'localStorage', {
167
+ value: asyncLocalStorage,
168
+ writable: false,
169
+ configurable: true,
170
+ })
171
+
172
+ console.log('[AsyncLocalStorage] Initialized - localStorage methods now return Promises')
173
+ }
174
+
175
+ export default asyncLocalStorage
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 自动代理模块 - 开发环境下自动拦截 fetch/XMLHttpRequest 请求并代理
3
+ *
4
+ * 原理:
5
+ * 1. 拦截所有 fetch/XHR 请求
6
+ * 2. 如果是外部 URL,转换为本地代理路径
7
+ * 3. 由 webpack-dev-server 代理到目标服务器
8
+ */
9
+
10
+ // 是否启用自动代理(仅开发环境)
11
+ const ENABLE_AUTO_PROXY = process.env.NODE_ENV === 'development'
12
+
13
+ // 需要代理的域名列表(可通过环境变量配置)
14
+ const PROXY_DOMAINS = (process.env.VUE_APP_PROXY_DOMAINS || '')
15
+ .split(',')
16
+ .map((d) => d.trim())
17
+ .filter(Boolean)
18
+
19
+ // 默认代理的常见域名模式
20
+ const DEFAULT_PROXY_PATTERNS = [
21
+ // 常见 API 域名
22
+ /api\./i,
23
+ /-api\./i,
24
+ /\.api\./i,
25
+ // CDN 和图片服务
26
+ /qcloudimg\./i,
27
+ /cdnott\./i,
28
+ /baidu\./i,
29
+ // 自定义域名(从环境变量读取)
30
+ ...PROXY_DOMAINS.map((d) => new RegExp(d.replace(/\./g, '\\.'), 'i')),
31
+ ]
32
+
33
+ /**
34
+ * 判断 URL 是否需要代理
35
+ */
36
+ function shouldProxy(url) {
37
+ if (!url) return false
38
+
39
+ try {
40
+ const urlObj = new URL(url, window.location.origin)
41
+
42
+ // 同源请求不需要代理
43
+ if (urlObj.origin === window.location.origin) {
44
+ return false
45
+ }
46
+
47
+ // 检查是否匹配代理模式
48
+ return DEFAULT_PROXY_PATTERNS.some((pattern) => pattern.test(urlObj.hostname))
49
+ } catch (e) {
50
+ return false
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 将外部 URL 转换为代理路径
56
+ */
57
+ function toProxyUrl(url) {
58
+ if (!url) return url
59
+
60
+ try {
61
+ const urlObj = new URL(url, window.location.origin)
62
+
63
+ // 构建代理路径: /proxy/{protocol}/{host}{pathname}{search}
64
+ const proxyPath = `/proxy/${urlObj.protocol.replace(':', '')}/${urlObj.host}${urlObj.pathname}${urlObj.search}`
65
+
66
+ console.log(`[AutoProxy] ${url} -> ${proxyPath}`)
67
+ return proxyPath
68
+ } catch (e) {
69
+ return url
70
+ }
71
+ }
72
+
73
+ /**
74
+ * 处理 URL(如果是外部 URL 则转换为代理路径)
75
+ */
76
+ export function proxyUrl(url) {
77
+ if (!ENABLE_AUTO_PROXY) return url
78
+ if (!shouldProxy(url)) return url
79
+ return toProxyUrl(url)
80
+ }
81
+
82
+ /**
83
+ * 初始化自动代理
84
+ */
85
+ export function initAutoProxy() {
86
+ if (!ENABLE_AUTO_PROXY) {
87
+ console.log('[AutoProxy] Disabled (not development mode)')
88
+ return
89
+ }
90
+
91
+ console.log('[AutoProxy] Initializing...')
92
+ console.log(
93
+ '[AutoProxy] Proxy patterns:',
94
+ DEFAULT_PROXY_PATTERNS.map((p) => p.toString())
95
+ )
96
+
97
+ // ===== 拦截 fetch =====
98
+ const originalFetch = window.fetch
99
+ window.fetch = function (input, init = {}) {
100
+ let url = typeof input === 'string' ? input : input.url
101
+
102
+ if (shouldProxy(url)) {
103
+ const proxyUrlStr = toProxyUrl(url)
104
+
105
+ // 更新 input
106
+ if (typeof input === 'string') {
107
+ input = proxyUrlStr
108
+ } else if (input instanceof Request) {
109
+ // 创建新的 Request 对象
110
+ input = new Request(proxyUrlStr, {
111
+ method: input.method,
112
+ headers: input.headers,
113
+ body: input.body,
114
+ mode: 'cors', // 使用代理时改为同源
115
+ credentials: input.credentials,
116
+ cache: input.cache,
117
+ redirect: input.redirect,
118
+ referrer: input.referrer,
119
+ integrity: input.integrity,
120
+ })
121
+ }
122
+ }
123
+
124
+ return originalFetch.call(this, input, init)
125
+ }
126
+
127
+ // ===== 拦截 XMLHttpRequest =====
128
+ const originalXHROpen = XMLHttpRequest.prototype.open
129
+ XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
130
+ if (shouldProxy(url)) {
131
+ url = toProxyUrl(url)
132
+ }
133
+ return originalXHROpen.call(this, method, url, async !== false, user, password)
134
+ }
135
+
136
+ // ===== 拦截 Image =====
137
+ const originalImageSrcDescriptor = Object.getOwnPropertyDescriptor(
138
+ HTMLImageElement.prototype,
139
+ 'src'
140
+ )
141
+ if (originalImageSrcDescriptor && originalImageSrcDescriptor.set) {
142
+ Object.defineProperty(HTMLImageElement.prototype, 'src', {
143
+ ...originalImageSrcDescriptor,
144
+ set(value) {
145
+ if (value.startsWith('http://127.0.0.1:38989')) {
146
+ return originalImageSrcDescriptor.set.call(
147
+ this,
148
+ value.replace('http://127.0.0.1:38989/', './')
149
+ )
150
+ }
151
+ const proxyValue = shouldProxy(value) ? toProxyUrl(value) : value
152
+ return originalImageSrcDescriptor.set.call(this, proxyValue)
153
+ },
154
+ })
155
+ }
156
+
157
+ console.log('[AutoProxy] Initialized successfully')
158
+ }
159
+
160
+ export default {
161
+ init: initAutoProxy,
162
+ proxyUrl,
163
+ shouldProxy,
164
+ toProxyUrl,
165
+ }
@@ -0,0 +1,84 @@
1
+ // Component Registry for callUIFunction support
2
+ // Local component registry for components that need callUIFunction support
3
+
4
+ let webEngine = null
5
+
6
+ const componentRegistry = new Map()
7
+
8
+ // SID (string id) to component mapping for ExtendModule.callUIFunction support
9
+ const sidRegistry = new Map()
10
+
11
+ // Set the web engine reference
12
+ export function setWebEngine(engine) {
13
+ webEngine = engine
14
+ }
15
+
16
+ export function getWebEngine() {
17
+ return webEngine
18
+ }
19
+
20
+ // Register a component by nodeId
21
+ export function registerComponent(nodeId, component) {
22
+ componentRegistry.set(nodeId, component)
23
+ console.log('[Web Renderer] Registered component:', nodeId, component.constructor.name)
24
+ }
25
+
26
+ // Register a component by sid (string id)
27
+ export function registerComponentBySid(sid, component) {
28
+ if (!sid || typeof sid !== 'string') {
29
+ return
30
+ }
31
+ sidRegistry.set(sid, component)
32
+ console.log(
33
+ '[Web Renderer] Registered component by sid:',
34
+ sid,
35
+ component.constructor?.name || component.tagName
36
+ )
37
+ }
38
+
39
+ // Unregister a component by sid
40
+ export function unregisterComponentBySid(sid) {
41
+ if (sidRegistry.has(sid)) {
42
+ sidRegistry.delete(sid)
43
+ console.log('[Web Renderer] Unregistered component by sid:', sid)
44
+ }
45
+ }
46
+
47
+ // Find component by sid (string id)
48
+ export function findComponentBySid(sid) {
49
+ if (sidRegistry.has(sid)) {
50
+ return sidRegistry.get(sid)
51
+ }
52
+ return null
53
+ }
54
+
55
+ // Find component by nodeId - check local registry first, then UIManagerModule
56
+ export function findComponentById(nodeId) {
57
+ // If nodeId is a string, it's likely a sid
58
+ if (typeof nodeId === 'string') {
59
+ return findComponentBySid(nodeId)
60
+ }
61
+
62
+ // Check local registry first
63
+ if (componentRegistry.has(nodeId)) {
64
+ return componentRegistry.get(nodeId)
65
+ }
66
+
67
+ if (!webEngine) {
68
+ return null
69
+ }
70
+
71
+ try {
72
+ // getModuleByName is on context, not engine
73
+ const uiManager = webEngine.context?.getModuleByName?.('UIManagerModule')
74
+ if (uiManager && uiManager.findViewById) {
75
+ const component = uiManager.findViewById(nodeId)
76
+ if (component) {
77
+ return component
78
+ }
79
+ }
80
+ } catch (e) {
81
+ console.error('[Web Renderer] Error finding component:', e)
82
+ }
83
+ return null
84
+ }
@@ -0,0 +1,6 @@
1
+ // TV app dimensions
2
+ export const TV_WIDTH = 1920
3
+ export const TV_HEIGHT = 1080
4
+
5
+ // App name for web renderer
6
+ export const APP_NAME = 'EsApp'
@@ -0,0 +1,8 @@
1
+ // Core module exports
2
+ export * from './constants'
3
+ export * from './componentRegistry'
4
+ export * from './SceneBuilder'
5
+ export * from './patches'
6
+ export * from './TVFocusManager'
7
+ export * from './autoProxy'
8
+ export * from './asyncLocalStorage'
@@ -0,0 +1,36 @@
1
+ // Module utility functions for web renderer
2
+
3
+ /**
4
+ * Resolve addon callback pattern used by HippyWebEngine
5
+ * Many native module methods receive an addon object as the last argument
6
+ * with a resolve function that should be called with the result.
7
+ *
8
+ * @param {Array} args - The arguments array passed to the module method
9
+ * @param {*} value - The value to resolve/return
10
+ * @returns {*} The value (also calls resolve if addon exists)
11
+ *
12
+ * @example
13
+ * async getDeviceInfo(...args) {
14
+ * return resolveAddon(args, { model: 'Web', ... })
15
+ * }
16
+ */
17
+ export function resolveAddon(args, value) {
18
+ const last = args && args.length ? args[args.length - 1] : null
19
+ if (last && typeof last.resolve === 'function') {
20
+ last.resolve(value)
21
+ }
22
+ return value
23
+ }
24
+
25
+ /**
26
+ * Create a standard module init method
27
+ * @param {Array} args - Arguments passed to init
28
+ * @returns {Promise<boolean>}
29
+ */
30
+ export function createModuleInit(args) {
31
+ const addon = args && args.length ? args[args.length - 1] : null
32
+ if (addon && typeof addon.resolve === 'function') {
33
+ addon.resolve(true)
34
+ }
35
+ return Promise.resolve(true)
36
+ }