@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,199 @@
1
+ import { QtBaseComponent } from './QtBaseComponent'
2
+
3
+ window.__HIPPY_COMPONENT_REGISTRY__ = window.__HIPPY_COMPONENT_REGISTRY__ || new Map()
4
+
5
+ /**
6
+ * QtProgressBar Component for web platform
7
+ */
8
+ export class QtProgressBar extends QtBaseComponent {
9
+ constructor(context, id, pId) {
10
+ super(context, id, pId)
11
+ this.tagName = 'TVProgressBarViewComponent'
12
+ this.dom = document.createElement('div')
13
+ this.dom.style.display = 'block'
14
+ this.dom.style.position = 'relative'
15
+ this.dom.style.overflow = 'hidden'
16
+ window.__HIPPY_COMPONENT_REGISTRY__.set(this.dom, this)
17
+
18
+ this._maxProgress = 100
19
+ this._progress = 0
20
+ this._secondaryProgress = 0
21
+ this._cornerRadius = 0
22
+ this._backgroundColor = '#CCCCCC'
23
+ this._progressColor = '#FF0000'
24
+
25
+ this._bgEl = null
26
+ this._secondaryEl = null
27
+ this._progressEl = null
28
+
29
+ this._initDOM()
30
+ }
31
+
32
+ _initDOM() {
33
+ // Background layer
34
+ this._bgEl = document.createElement('div')
35
+ this._bgEl.style.position = 'absolute'
36
+ this._bgEl.style.top = '0'
37
+ this._bgEl.style.left = '0'
38
+ this._bgEl.style.width = '100%'
39
+ this._bgEl.style.height = '100%'
40
+ this._bgEl.style.boxSizing = 'border-box'
41
+ this.dom.appendChild(this._bgEl)
42
+
43
+ // Secondary progress layer
44
+ this._secondaryEl = document.createElement('div')
45
+ this._secondaryEl.style.position = 'absolute'
46
+ this._secondaryEl.style.top = '0'
47
+ this._secondaryEl.style.left = '0'
48
+ this._secondaryEl.style.height = '100%'
49
+ this._secondaryEl.style.boxSizing = 'border-box'
50
+ this.dom.appendChild(this._secondaryEl)
51
+
52
+ // Progress layer
53
+ this._progressEl = document.createElement('div')
54
+ this._progressEl.style.position = 'absolute'
55
+ this._progressEl.style.top = '0'
56
+ this._progressEl.style.left = '0'
57
+ this._progressEl.style.height = '100%'
58
+ this._progressEl.style.boxSizing = 'border-box'
59
+ this.dom.appendChild(this._progressEl)
60
+ }
61
+
62
+ _updateStyles() {
63
+ const radius = this._cornerRadius + 'px'
64
+
65
+ // Background
66
+ this._bgEl.style.backgroundColor = this._backgroundColor
67
+ this._bgEl.style.borderRadius = radius
68
+
69
+ // Secondary progress
70
+ const secondaryWidth = (this._secondaryProgress / this._maxProgress) * 100 + '%'
71
+ this._secondaryEl.style.width = secondaryWidth
72
+ this._secondaryEl.style.backgroundColor = this._parseColor(this._progressColor, 0.3)
73
+ this._secondaryEl.style.borderRadius = radius
74
+
75
+ // Progress
76
+ const progressWidth = (this._progress / this._maxProgress) * 100 + '%'
77
+ this._progressEl.style.width = progressWidth
78
+ this._applyProgressColor()
79
+ this._progressEl.style.borderRadius = radius
80
+ }
81
+
82
+ _parseColor(color, alpha) {
83
+ if (typeof color === 'string') {
84
+ // If it's a hex color
85
+ if (color.startsWith('#')) {
86
+ const hex = color.slice(1)
87
+ const r = parseInt(hex.substr(0, 2), 16)
88
+ const g = parseInt(hex.substr(2, 2), 16)
89
+ const b = parseInt(hex.substr(4, 2), 16)
90
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`
91
+ }
92
+ return color
93
+ }
94
+ return color
95
+ }
96
+
97
+ _applyProgressColor() {
98
+ const color = this._progressColor
99
+
100
+ if (color && typeof color === 'object' && color.startColor && color.endColor) {
101
+ // Gradient color
102
+ const type = color.type || 0 // 0: linear, 1: radial
103
+ const shape = color.shape || 0 // 0: rectangle, 1: oval
104
+ const start = color.startColor
105
+ const end = color.endColor
106
+
107
+ if (type === 0) {
108
+ // Linear gradient - horizontal
109
+ this._progressEl.style.background = `linear-gradient(to right, ${start}, ${end})`
110
+ } else {
111
+ // Radial gradient
112
+ this._progressEl.style.background = `radial-gradient(circle, ${start}, ${end})`
113
+ }
114
+ } else if (typeof color === 'string') {
115
+ this._progressEl.style.backgroundColor = color
116
+ } else if (typeof color === 'number') {
117
+ // Convert int color to hex
118
+ const a = ((color >> 24) & 0xff) / 255
119
+ const r = (color >> 16) & 0xff
120
+ const g = (color >> 8) & 0xff
121
+ const b = color & 0xff
122
+ this._progressEl.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`
123
+ }
124
+ }
125
+
126
+ updateProperty(key, value) {
127
+ switch (key) {
128
+ case 'maxProgress':
129
+ case 'max-progress':
130
+ this._maxProgress = value || 100
131
+ this._updateStyles()
132
+ break
133
+ case 'progress':
134
+ this._progress = value || 0
135
+ this._updateStyles()
136
+ break
137
+ case 'secondaryProgress':
138
+ case 'secondary-progress':
139
+ this._secondaryProgress = value || 0
140
+ this._updateStyles()
141
+ break
142
+ case 'cornerRadius':
143
+ case 'corner-radius':
144
+ this._cornerRadius = value || 0
145
+ this._updateStyles()
146
+ break
147
+ case 'backgroundColor':
148
+ case 'background-color':
149
+ this._backgroundColor = value || '#CCCCCC'
150
+ this._updateStyles()
151
+ break
152
+ case 'color':
153
+ this._progressColor = value
154
+ this._updateStyles()
155
+ break
156
+ default:
157
+ super.updateProperty(key, value)
158
+ }
159
+ }
160
+
161
+ // API methods called via callUIFunction
162
+ setMaxProgress(params) {
163
+ this._maxProgress = params[0] || 100
164
+ this._updateStyles()
165
+ }
166
+
167
+ setProgress(params) {
168
+ this._progress = params[0] || 0
169
+ this._updateStyles()
170
+ }
171
+
172
+ setSecondaryProgress(params) {
173
+ this._secondaryProgress = params[0] || 0
174
+ this._updateStyles()
175
+ }
176
+
177
+ setProgressColor(params) {
178
+ this._progressColor = params[0]
179
+ this._updateStyles()
180
+ }
181
+
182
+ setProgressDefaultColor(params) {
183
+ this._backgroundColor = params[0]
184
+ this._updateStyles()
185
+ }
186
+
187
+ init(props) {
188
+ super.init(props)
189
+
190
+ if (props.maxProgress !== undefined) this._maxProgress = props.maxProgress
191
+ if (props.progress !== undefined) this._progress = props.progress
192
+ if (props.secondaryProgress !== undefined) this._secondaryProgress = props.secondaryProgress
193
+ if (props.cornerRadius !== undefined) this._cornerRadius = props.cornerRadius
194
+ if (props.backgroundColor !== undefined) this._backgroundColor = props.backgroundColor
195
+ if (props.color !== undefined) this._progressColor = props.color
196
+
197
+ this._updateStyles()
198
+ }
199
+ }
@@ -0,0 +1,78 @@
1
+ import { QtBaseComponent } from './QtBaseComponent'
2
+
3
+ window.__HIPPY_COMPONENT_REGISTRY__ = window.__HIPPY_COMPONENT_REGISTRY__ || new Map()
4
+
5
+ // Cache for qrcode library
6
+ let QRCodeLib = null
7
+
8
+ /**
9
+ * QtQRCode Component using qrcode library (web only)
10
+ */
11
+ export class QtQRCode extends QtBaseComponent {
12
+ constructor(context, id, pId) {
13
+ super(context, id, pId)
14
+ this.tagName = 'QrCodeComponent'
15
+ this.dom = document.createElement('div')
16
+ this.dom.style.display = 'block'
17
+ window.__HIPPY_COMPONENT_REGISTRY__.set(this.dom, this)
18
+ this._content = ''
19
+ }
20
+
21
+ async _render() {
22
+ if (!this._content || !this.dom) return
23
+
24
+ // Lazy load qrcode library (web platform only)
25
+ if (!QRCodeLib) {
26
+ try {
27
+ QRCodeLib = await import('qrcode').then((m) => m.default || m)
28
+ } catch (e) {
29
+ console.error('[QtQRCode] Failed to load qrcode library:', e)
30
+ return
31
+ }
32
+ }
33
+
34
+ const rect = this.dom.getBoundingClientRect()
35
+ const size = Math.max(rect.width || 200, rect.height || 200)
36
+
37
+ QRCodeLib.toDataURL(
38
+ this._content,
39
+ {
40
+ width: size,
41
+ margin: 2,
42
+ color: {
43
+ dark: '#000000',
44
+ light: '#FFFFFF',
45
+ },
46
+ },
47
+ (err, url) => {
48
+ if (err) {
49
+ console.error('[QtQRCode] Error:', err)
50
+ return
51
+ }
52
+
53
+ this.dom.innerHTML = ''
54
+ const img = document.createElement('img')
55
+ img.src = url
56
+ img.style.width = '100%'
57
+ img.style.height = '100%'
58
+ img.style.display = 'block'
59
+ this.dom.appendChild(img)
60
+ }
61
+ )
62
+ }
63
+
64
+ updateProperty(key, value) {
65
+ if (key === 'content') {
66
+ this._content = value || ''
67
+ this._render()
68
+ } else {
69
+ super.updateProperty(key, value)
70
+ }
71
+ }
72
+
73
+ init(props) {
74
+ super.init(props)
75
+ this._content = props.content || ''
76
+ this._render()
77
+ }
78
+ }
@@ -0,0 +1,149 @@
1
+ // QtReplaceChild - Web adapter for qt-replace-child
2
+ // In native Android, this component marks a placeholder position for view replacement
3
+ // On web platform, we simplify it as a container with SID support
4
+
5
+ import { QtBaseComponent } from './QtBaseComponent'
6
+ import {
7
+ registerComponent,
8
+ registerComponentBySid,
9
+ unregisterComponentBySid,
10
+ } from '../core/componentRegistry'
11
+
12
+ export class QtReplaceChild extends QtBaseComponent {
13
+ constructor(context, id, pId) {
14
+ super(context, id, pId)
15
+ this.tagName = 'QtReplaceChild'
16
+
17
+ // Create a div as placeholder container
18
+ this.dom = document.createElement('div')
19
+ this.dom.style.cssText = `
20
+ position: absolute;
21
+ width: 1920px;
22
+ height: 1080px;
23
+ background-color: transparent;
24
+ `
25
+
26
+ // State
27
+ this._markChildSID = ''
28
+ this._replaceOnVisibilityChanged = false
29
+ this._currentSid = null
30
+
31
+ // Register component
32
+ registerComponent(id, this)
33
+ }
34
+
35
+ /**
36
+ * Mark child SID - the target view that will be replaced into this position
37
+ * In native, this enables the replacement mechanism
38
+ * On web, we just store it for reference
39
+ */
40
+ setMarkChildSID(sid) {
41
+ this._markChildSID = sid
42
+ console.log('[QtReplaceChild] markChildSID set to:', sid)
43
+ }
44
+
45
+ /**
46
+ * Set child SID dynamically - triggers view replacement
47
+ * In native, this moves the target view to this placeholder
48
+ * On web, we can simulate by finding and cloning the target
49
+ */
50
+ setChildSID(sid) {
51
+ console.log('[QtReplaceChild] setChildSID:', sid)
52
+
53
+ if (!sid) return
54
+
55
+ // Find the target component by SID
56
+ const targetComponent = this._findComponentBySID(sid)
57
+
58
+ if (targetComponent && targetComponent.dom) {
59
+ console.log('[QtReplaceChild] Found target component, cloning to placeholder')
60
+
61
+ // Clear current content
62
+ this.dom.innerHTML = ''
63
+
64
+ // Clone the target DOM and append
65
+ const clonedDom = targetComponent.dom.cloneNode(true)
66
+ this.dom.appendChild(clonedDom)
67
+ } else {
68
+ console.log('[QtReplaceChild] Target component not found for SID:', sid)
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Set replace on visibility changed
74
+ */
75
+ setReplaceOnVisibilityChanged(value) {
76
+ this._replaceOnVisibilityChanged = value
77
+ }
78
+
79
+ /**
80
+ * Handle property updates
81
+ */
82
+ updateProperty(key, value) {
83
+ // Handle sid (string id) property - register to sidRegistry
84
+ if (key === 'sid') {
85
+ // Unregister previous sid if exists
86
+ if (this._currentSid) {
87
+ unregisterComponentBySid(this._currentSid)
88
+ }
89
+
90
+ // Register new sid
91
+ this._currentSid = value
92
+ if (value) {
93
+ registerComponentBySid(value, this)
94
+ // Also set as DOM attribute for debugging
95
+ this.dom.setAttribute('sid', String(value))
96
+ } else {
97
+ this.dom.removeAttribute('sid')
98
+ }
99
+ return
100
+ }
101
+
102
+ switch (key) {
103
+ case 'markChildSID':
104
+ case 'markChildSid':
105
+ this.setMarkChildSID(value)
106
+ break
107
+ case 'replaceOnVisibilityChanged':
108
+ this.setReplaceOnVisibilityChanged(value)
109
+ break
110
+ default:
111
+ super.updateProperty(key, value)
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Find component by SID from global registry
117
+ */
118
+ _findComponentBySID(sid) {
119
+ // Import findComponentBySid from componentRegistry
120
+ const { findComponentBySid } = require('../core/componentRegistry')
121
+
122
+ // First try using the official registry
123
+ const component = findComponentBySid(sid)
124
+ if (component) {
125
+ return component
126
+ }
127
+
128
+ // Fallback: Try to find from DOM by sid attribute
129
+ const domElement = document.querySelector(`[sid="${sid}"]`)
130
+ if (domElement) {
131
+ return { dom: domElement }
132
+ }
133
+
134
+ return null
135
+ }
136
+
137
+ // Called when component is unmounted - cleanup sid registration
138
+ beforeUnmount() {
139
+ if (this._currentSid) {
140
+ unregisterComponentBySid(this._currentSid)
141
+ this._currentSid = null
142
+ }
143
+ }
144
+
145
+ // Legacy destroy method
146
+ destroy() {
147
+ this.beforeUnmount?.()
148
+ }
149
+ }
@@ -0,0 +1,166 @@
1
+ // QtRippleView - A ripple animation view component
2
+ import { QtBaseComponent } from './QtBaseComponent'
3
+ import { registerComponent } from '../core/componentRegistry'
4
+
5
+ export class QtRippleView extends QtBaseComponent {
6
+ constructor(context, id, pId) {
7
+ super(context, id, pId)
8
+ this.tagName = 'RippleViewComponent'
9
+ this.dom = document.createElement('div')
10
+ this.dom.setAttribute('data-component-name', 'QtRippleView')
11
+
12
+ this.dom.style.cssText = `
13
+ display: block;
14
+ position: relative;
15
+ box-sizing: border-box;
16
+ overflow: hidden;
17
+ `
18
+
19
+ this._isShowRipple = false
20
+ this._rippleVisible = 'visible'
21
+ this._color = '#FF4E46'
22
+ this._animationFrame = null
23
+
24
+ registerComponent(id, this)
25
+ }
26
+
27
+ defaultStyle() {
28
+ return {
29
+ display: 'block',
30
+ position: 'relative',
31
+ boxSizing: 'border-box',
32
+ overflow: 'hidden',
33
+ }
34
+ }
35
+
36
+ updateProperty(key, value) {
37
+ switch (key) {
38
+ case 'isShowRipple':
39
+ case 'isshowripple':
40
+ this._isShowRipple = value === true || value === 'true'
41
+ this._updateRippleAnimation()
42
+ break
43
+ case 'rippleVisible':
44
+ case 'ripplevisible':
45
+ this._rippleVisible = value || 'visible'
46
+ this.dom.style.visibility = this._rippleVisible
47
+ break
48
+ case 'color':
49
+ this._color = value || '#FF4E46'
50
+ this._updateRippleColor()
51
+ break
52
+ case 'delayLoad':
53
+ case 'delayload':
54
+ if (value > 0) {
55
+ setTimeout(() => {
56
+ this._startRippleAnimation()
57
+ }, value)
58
+ } else {
59
+ this._startRippleAnimation()
60
+ }
61
+ break
62
+ default:
63
+ super.updateProperty(key, value)
64
+ }
65
+ }
66
+
67
+ _updateRippleAnimation() {
68
+ if (this._isShowRipple) {
69
+ this._startRippleAnimation()
70
+ } else {
71
+ this._stopRippleAnimation()
72
+ }
73
+ }
74
+
75
+ _updateRippleColor() {
76
+ const ripples = this.dom.querySelectorAll('.ripple-circle')
77
+ ripples.forEach((ripple) => {
78
+ ripple.style.backgroundColor = this._color
79
+ })
80
+ }
81
+
82
+ _startRippleAnimation() {
83
+ if (!this._isShowRipple) return
84
+
85
+ this._clearRipples()
86
+
87
+ const rect = this.dom.getBoundingClientRect()
88
+ const size = Math.max(rect.width, rect.height)
89
+ const centerX = rect.width / 2
90
+ const centerY = rect.height / 2
91
+
92
+ for (let i = 0; i < 3; i++) {
93
+ setTimeout(() => {
94
+ if (!this._isShowRipple) return
95
+ this._createRippleCircle(centerX, centerY, size, i)
96
+ }, i * 300)
97
+ }
98
+ }
99
+
100
+ _createRippleCircle(centerX, centerY, size, index) {
101
+ const ripple = document.createElement('div')
102
+ ripple.className = 'ripple-circle'
103
+ ripple.style.cssText = `
104
+ position: absolute;
105
+ left: ${centerX}px;
106
+ top: ${centerY}px;
107
+ width: 0;
108
+ height: 0;
109
+ border-radius: 50%;
110
+ background-color: ${this._color};
111
+ opacity: 0.6;
112
+ transform: translate(-50%, -50%);
113
+ pointer-events: none;
114
+ `
115
+ this.dom.appendChild(ripple)
116
+
117
+ let progress = 0
118
+ const maxProgress = size * 1.5
119
+ const duration = 1500 + index * 200
120
+
121
+ const animate = () => {
122
+ if (!this._isShowRipple) {
123
+ ripple.remove()
124
+ return
125
+ }
126
+
127
+ progress += (maxProgress / duration) * 16
128
+
129
+ if (progress < maxProgress) {
130
+ const opacity = 0.6 * (1 - progress / maxProgress)
131
+
132
+ ripple.style.width = `${progress}px`
133
+ ripple.style.height = `${progress}px`
134
+ ripple.style.opacity = opacity.toString()
135
+
136
+ this._animationFrame = requestAnimationFrame(animate)
137
+ } else {
138
+ ripple.style.width = '0'
139
+ ripple.style.height = '0'
140
+ ripple.style.opacity = '0.6'
141
+ progress = 0
142
+ this._animationFrame = requestAnimationFrame(animate)
143
+ }
144
+ }
145
+
146
+ animate()
147
+ }
148
+
149
+ _stopRippleAnimation() {
150
+ if (this._animationFrame) {
151
+ cancelAnimationFrame(this._animationFrame)
152
+ this._animationFrame = null
153
+ }
154
+ this._clearRipples()
155
+ }
156
+
157
+ _clearRipples() {
158
+ const ripples = this.dom.querySelectorAll('.ripple-circle')
159
+ ripples.forEach((ripple) => ripple.remove())
160
+ }
161
+
162
+ destroy() {
163
+ this._stopRippleAnimation()
164
+ super.destroy()
165
+ }
166
+ }