livefootballtv-tizen 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/app/app-es5.js +1976 -0
- package/app/config.xml +15 -0
- package/app/css/controls.css +54 -0
- package/app/icon.png +0 -0
- package/app/index.html +318 -0
- package/app/js/crypto-js.min.js +1 -0
- package/app/js/hls.min.js +2 -0
- package/app/js/shaka-player.compiled.min.js +1 -0
- package/app/js/shaka-player.ui.debug.js +3068 -0
- package/app/native_service/README.md +94 -0
- package/app/native_service/inc/proxy_service.h +22 -0
- package/app/native_service/src/proxy_service.cpp +453 -0
- package/app/native_service/tizen-manifest.xml +23 -0
- package/app/navigation.js +663 -0
- package/app/styles.css +539 -0
- package/package.json +11 -0
- package/service.js +121 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
// navigation.js - ES5 D-Pad Navigation for Tizen v4
|
|
2
|
+
// Handles arrow key navigation for categories, posts grid, and player controls
|
|
3
|
+
|
|
4
|
+
(function (window) {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
var DPadNavigator = {
|
|
8
|
+
currentZone: 'categories', // 'categories', 'posts', 'player', 'settings'
|
|
9
|
+
focusedCategoryIndex: 0,
|
|
10
|
+
focusedPostIndex: 0,
|
|
11
|
+
focusedPlayerControlIndex: 0,
|
|
12
|
+
lastFocusedPostIndex: 0,
|
|
13
|
+
gridColumns: 4, // Default columns, will be calculated dynamically
|
|
14
|
+
isTransitioning: false, // Flag to prevent key handling during transitions
|
|
15
|
+
|
|
16
|
+
init: function () {
|
|
17
|
+
var self = this;
|
|
18
|
+
console.log('DPadNavigator: Initializing');
|
|
19
|
+
|
|
20
|
+
// Global keydown handler
|
|
21
|
+
document.addEventListener('keydown', function (e) {
|
|
22
|
+
self.handleKeyDown(e);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Focus first category on load
|
|
26
|
+
setTimeout(function () {
|
|
27
|
+
self.focusCategory(0);
|
|
28
|
+
console.log('DPadNavigator: Initial focus set to category 0');
|
|
29
|
+
}, 500);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
handleKeyDown: function (e) {
|
|
33
|
+
var key = e.key || e.keyCode;
|
|
34
|
+
var keyName = typeof key === 'string' ? key : this.getKeyName(key);
|
|
35
|
+
|
|
36
|
+
// Ignore key repeat for Enter key to prevent multiple triggers
|
|
37
|
+
if (e.repeat && keyName === 'Enter') {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Block all input during transitions
|
|
44
|
+
if (this.isTransitioning) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
e.stopPropagation();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Don't interfere with text input (except for Escape)
|
|
51
|
+
if (e.target.tagName === 'INPUT' && keyName !== 'Escape') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Don't interfere with select elements
|
|
56
|
+
if (e.target.tagName === 'SELECT') {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var handled = false;
|
|
61
|
+
|
|
62
|
+
switch (this.currentZone) {
|
|
63
|
+
case 'categories':
|
|
64
|
+
handled = this.handleCategoryNavigation(keyName, e);
|
|
65
|
+
break;
|
|
66
|
+
case 'posts':
|
|
67
|
+
handled = this.handlePostsNavigation(keyName, e);
|
|
68
|
+
break;
|
|
69
|
+
case 'player':
|
|
70
|
+
handled = this.handlePlayerNavigation(keyName, e);
|
|
71
|
+
break;
|
|
72
|
+
case 'settings':
|
|
73
|
+
handled = this.handleSettingsNavigation(keyName, e);
|
|
74
|
+
break;
|
|
75
|
+
case 'display':
|
|
76
|
+
handled = this.handleDisplayNavigation(keyName, e);
|
|
77
|
+
break;
|
|
78
|
+
case 'header':
|
|
79
|
+
handled = this.handleHeaderNavigation(keyName, e);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (handled) {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
getKeyName: function (keyCode) {
|
|
90
|
+
var keys = {
|
|
91
|
+
37: 'ArrowLeft',
|
|
92
|
+
38: 'ArrowUp',
|
|
93
|
+
39: 'ArrowRight',
|
|
94
|
+
40: 'ArrowDown',
|
|
95
|
+
13: 'Enter',
|
|
96
|
+
27: 'Escape',
|
|
97
|
+
8: 'Backspace',
|
|
98
|
+
10009: 'Back' // Tizen back button
|
|
99
|
+
};
|
|
100
|
+
return keys[keyCode] || '';
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
handleCategoryNavigation: function (key, event) {
|
|
104
|
+
var categories = document.querySelectorAll('.category');
|
|
105
|
+
if (!categories.length) return false;
|
|
106
|
+
|
|
107
|
+
switch (key) {
|
|
108
|
+
case 'ArrowDown':
|
|
109
|
+
this.focusedCategoryIndex = Math.min(this.focusedCategoryIndex + 1, categories.length - 1);
|
|
110
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
111
|
+
return true;
|
|
112
|
+
|
|
113
|
+
case 'ArrowUp':
|
|
114
|
+
if (this.focusedCategoryIndex === 0) {
|
|
115
|
+
// Move to proxy indicator in header
|
|
116
|
+
var proxyIndicator = document.getElementById('proxyStatusIndicator');
|
|
117
|
+
if (proxyIndicator) {
|
|
118
|
+
this.setZone('header');
|
|
119
|
+
proxyIndicator.focus();
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
this.focusedCategoryIndex = Math.max(this.focusedCategoryIndex - 1, 0);
|
|
123
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
|
|
127
|
+
case 'ArrowRight':
|
|
128
|
+
// Move to posts
|
|
129
|
+
this.setZone('posts');
|
|
130
|
+
this.focusPost(this.focusedPostIndex);
|
|
131
|
+
return true;
|
|
132
|
+
|
|
133
|
+
case 'Enter':
|
|
134
|
+
if (categories[this.focusedCategoryIndex]) {
|
|
135
|
+
console.log('DPadNavigator: Clicking category ' + this.focusedCategoryIndex);
|
|
136
|
+
categories[this.focusedCategoryIndex].click();
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
handlePostsNavigation: function (key, event) {
|
|
144
|
+
var posts = document.querySelectorAll('.post');
|
|
145
|
+
if (!posts.length) {
|
|
146
|
+
console.log('DPadNavigator: No posts found');
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Calculate columns dynamically based on window width
|
|
151
|
+
this.calculateGridColumns();
|
|
152
|
+
|
|
153
|
+
switch (key) {
|
|
154
|
+
case 'ArrowRight':
|
|
155
|
+
if ((this.focusedPostIndex + 1) % this.gridColumns === 0) {
|
|
156
|
+
// End of row, wrap to next row
|
|
157
|
+
if (this.focusedPostIndex + 1 < posts.length) {
|
|
158
|
+
this.focusedPostIndex++;
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
this.focusedPostIndex = Math.min(this.focusedPostIndex + 1, posts.length - 1);
|
|
162
|
+
}
|
|
163
|
+
this.focusPost(this.focusedPostIndex);
|
|
164
|
+
return true;
|
|
165
|
+
|
|
166
|
+
case 'ArrowLeft':
|
|
167
|
+
if (this.focusedPostIndex % this.gridColumns === 0) {
|
|
168
|
+
// Start of row, move to categories
|
|
169
|
+
this.setZone('categories');
|
|
170
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
171
|
+
} else {
|
|
172
|
+
this.focusedPostIndex = Math.max(this.focusedPostIndex - 1, 0);
|
|
173
|
+
this.focusPost(this.focusedPostIndex);
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
|
|
177
|
+
case 'ArrowDown':
|
|
178
|
+
var nextIndex = this.focusedPostIndex + this.gridColumns;
|
|
179
|
+
if (nextIndex < posts.length) {
|
|
180
|
+
this.focusedPostIndex = nextIndex;
|
|
181
|
+
this.focusPost(this.focusedPostIndex);
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
|
|
185
|
+
case 'ArrowUp':
|
|
186
|
+
var prevIndex = this.focusedPostIndex - this.gridColumns;
|
|
187
|
+
if (prevIndex >= 0) {
|
|
188
|
+
this.focusedPostIndex = prevIndex;
|
|
189
|
+
this.focusPost(this.focusedPostIndex);
|
|
190
|
+
} else {
|
|
191
|
+
// Move to proxy indicator in header
|
|
192
|
+
var proxyIndicator = document.getElementById('proxyStatusIndicator');
|
|
193
|
+
if (proxyIndicator) {
|
|
194
|
+
this.setZone('header');
|
|
195
|
+
proxyIndicator.focus();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
|
|
200
|
+
case 'Enter':
|
|
201
|
+
if (posts[this.focusedPostIndex]) {
|
|
202
|
+
console.log('DPadNavigator: Opening post ' + this.focusedPostIndex);
|
|
203
|
+
this.lastFocusedPostIndex = this.focusedPostIndex;
|
|
204
|
+
posts[this.focusedPostIndex].click();
|
|
205
|
+
// Note: enterPlayerMode will be called from openPost in app-es5.js
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
|
|
209
|
+
case 'Escape':
|
|
210
|
+
case 'Back':
|
|
211
|
+
case 'Backspace':
|
|
212
|
+
// Go back to categories
|
|
213
|
+
this.setZone('categories');
|
|
214
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
handlePlayerNavigation: function (key, event) {
|
|
221
|
+
var modal = document.getElementById('videoPlayerModal');
|
|
222
|
+
|
|
223
|
+
// Check if modal is visible
|
|
224
|
+
var isModalVisible = modal && (
|
|
225
|
+
modal.style.display === 'flex' ||
|
|
226
|
+
modal.style.display === 'block' ||
|
|
227
|
+
(!modal.hidden && modal.style.display !== 'none')
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (!isModalVisible) {
|
|
231
|
+
console.log('DPadNavigator: Player modal not visible, switching to posts');
|
|
232
|
+
this.setZone('posts');
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
var closeBtn = document.getElementById('closePlayer');
|
|
237
|
+
var video = document.getElementById('videoPlayer');
|
|
238
|
+
|
|
239
|
+
// Get Shaka UI overflow menu and its button
|
|
240
|
+
var overflowMenuBtn = modal.querySelector('.shaka-overflow-menu-button');
|
|
241
|
+
var overflowMenu = modal.querySelector('.shaka-overflow-menu');
|
|
242
|
+
var isOverflowOpen = overflowMenu && overflowMenu.classList.contains('shaka-displayed');
|
|
243
|
+
|
|
244
|
+
// Get Shaka UI control panel buttons
|
|
245
|
+
var controlPanel = modal.querySelector('.shaka-controls-button-panel');
|
|
246
|
+
var controlButtons = controlPanel ? controlPanel.querySelectorAll('button') : [];
|
|
247
|
+
|
|
248
|
+
// Debug logging - only log once per player session
|
|
249
|
+
if (!window.shakaNavDebugLogged) {
|
|
250
|
+
console.log('DPadNavigator: Shaka UI Debug');
|
|
251
|
+
console.log(' - Control panel found:', !!controlPanel);
|
|
252
|
+
console.log(' - Control buttons found:', controlButtons.length);
|
|
253
|
+
console.log(' - Overflow menu button found:', !!overflowMenuBtn);
|
|
254
|
+
console.log(' - Overflow menu found:', !!overflowMenu);
|
|
255
|
+
if (controlButtons.length > 0) {
|
|
256
|
+
for (var k = 0; k < controlButtons.length; k++) {
|
|
257
|
+
console.log(' - Button ' + k + ':', controlButtons[k].className);
|
|
258
|
+
// Make buttons focusable for D-pad navigation
|
|
259
|
+
controlButtons[k].tabIndex = 0;
|
|
260
|
+
// Add focus event to show visual indicator
|
|
261
|
+
controlButtons[k].onfocus = function () {
|
|
262
|
+
this.style.outline = '3px solid #3ea6ff';
|
|
263
|
+
this.style.outlineOffset = '2px';
|
|
264
|
+
};
|
|
265
|
+
controlButtons[k].onblur = function () {
|
|
266
|
+
this.style.outline = 'none';
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
window.shakaNavDebugLogged = true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Handle navigation within Shaka overflow menu when open
|
|
274
|
+
if (isOverflowOpen && overflowMenu) {
|
|
275
|
+
var menuButtons = overflowMenu.querySelectorAll('button');
|
|
276
|
+
var currentFocused = document.activeElement;
|
|
277
|
+
var currentIndex = -1;
|
|
278
|
+
for (var i = 0; i < menuButtons.length; i++) {
|
|
279
|
+
if (menuButtons[i] === currentFocused) {
|
|
280
|
+
currentIndex = i;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
switch (key) {
|
|
286
|
+
case 'ArrowDown':
|
|
287
|
+
var nextIdx = Math.min(currentIndex + 1, menuButtons.length - 1);
|
|
288
|
+
if (nextIdx >= 0 && menuButtons[nextIdx]) menuButtons[nextIdx].focus();
|
|
289
|
+
return true;
|
|
290
|
+
|
|
291
|
+
case 'ArrowUp':
|
|
292
|
+
if (currentIndex <= 0) {
|
|
293
|
+
// Close menu and focus overflow button
|
|
294
|
+
if (overflowMenuBtn) overflowMenuBtn.click();
|
|
295
|
+
} else {
|
|
296
|
+
menuButtons[currentIndex - 1].focus();
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
|
|
300
|
+
case 'Enter':
|
|
301
|
+
if (currentFocused) currentFocused.click();
|
|
302
|
+
return true;
|
|
303
|
+
|
|
304
|
+
case 'Escape':
|
|
305
|
+
case 'Back':
|
|
306
|
+
case 'Backspace':
|
|
307
|
+
// Close overflow menu
|
|
308
|
+
if (overflowMenuBtn) overflowMenuBtn.click();
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Build focusable elements list: close button, video, then Shaka control buttons
|
|
315
|
+
var focusableElements = [];
|
|
316
|
+
if (closeBtn) focusableElements.push(closeBtn);
|
|
317
|
+
if (video) focusableElements.push(video);
|
|
318
|
+
for (var j = 0; j < controlButtons.length; j++) {
|
|
319
|
+
focusableElements.push(controlButtons[j]);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if (!focusableElements.length) return false;
|
|
324
|
+
|
|
325
|
+
// Clamp index to valid range
|
|
326
|
+
this.focusedPlayerControlIndex = Math.max(0, Math.min(this.focusedPlayerControlIndex, focusableElements.length - 1));
|
|
327
|
+
|
|
328
|
+
switch (key) {
|
|
329
|
+
case 'ArrowRight':
|
|
330
|
+
this.focusedPlayerControlIndex = Math.min(this.focusedPlayerControlIndex + 1, focusableElements.length - 1);
|
|
331
|
+
focusableElements[this.focusedPlayerControlIndex].focus();
|
|
332
|
+
return true;
|
|
333
|
+
|
|
334
|
+
case 'ArrowLeft':
|
|
335
|
+
this.focusedPlayerControlIndex = Math.max(this.focusedPlayerControlIndex - 1, 0);
|
|
336
|
+
focusableElements[this.focusedPlayerControlIndex].focus();
|
|
337
|
+
return true;
|
|
338
|
+
|
|
339
|
+
case 'ArrowDown':
|
|
340
|
+
// If on video (index 1), move to first control button
|
|
341
|
+
if (this.focusedPlayerControlIndex === 1 && controlButtons.length > 0) {
|
|
342
|
+
this.focusedPlayerControlIndex = 2;
|
|
343
|
+
focusableElements[this.focusedPlayerControlIndex].focus();
|
|
344
|
+
} else {
|
|
345
|
+
this.focusedPlayerControlIndex = Math.min(this.focusedPlayerControlIndex + 1, focusableElements.length - 1);
|
|
346
|
+
focusableElements[this.focusedPlayerControlIndex].focus();
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
|
|
350
|
+
case 'ArrowUp':
|
|
351
|
+
// If on control buttons (index >= 2), move to video
|
|
352
|
+
if (this.focusedPlayerControlIndex >= 2) {
|
|
353
|
+
this.focusedPlayerControlIndex = 1;
|
|
354
|
+
if (video) video.focus();
|
|
355
|
+
} else {
|
|
356
|
+
this.focusedPlayerControlIndex = Math.max(this.focusedPlayerControlIndex - 1, 0);
|
|
357
|
+
focusableElements[this.focusedPlayerControlIndex].focus();
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
case 'Enter':
|
|
363
|
+
var focused = focusableElements[this.focusedPlayerControlIndex];
|
|
364
|
+
// If focused on video, toggle play/pause
|
|
365
|
+
if (focused === video) {
|
|
366
|
+
if (video.paused) {
|
|
367
|
+
video.play().catch(function (e) { console.log('Play failed:', e); });
|
|
368
|
+
this.showPlayPauseIndicator('▶');
|
|
369
|
+
} else {
|
|
370
|
+
video.pause();
|
|
371
|
+
this.showPlayPauseIndicator('⏸');
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
// Otherwise click the focused element
|
|
376
|
+
if (focused) {
|
|
377
|
+
focused.click();
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
|
|
381
|
+
case 'Escape':
|
|
382
|
+
case 'Back':
|
|
383
|
+
case 'Backspace':
|
|
384
|
+
console.log('DPadNavigator: Closing player');
|
|
385
|
+
if (typeof window.closePlayer === 'function') {
|
|
386
|
+
window.closePlayer();
|
|
387
|
+
} else if (closeBtn) {
|
|
388
|
+
closeBtn.click();
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// Show play/pause indicator overlay
|
|
396
|
+
showPlayPauseIndicator: function (symbol) {
|
|
397
|
+
var video = document.getElementById('videoPlayer');
|
|
398
|
+
if (!video) return;
|
|
399
|
+
|
|
400
|
+
var container = video.parentElement;
|
|
401
|
+
var indicator = container.querySelector('.play-pause-indicator');
|
|
402
|
+
if (!indicator) {
|
|
403
|
+
indicator = document.createElement('div');
|
|
404
|
+
indicator.className = 'play-pause-indicator';
|
|
405
|
+
indicator.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:80px;color:#fff;background:rgba(0,0,0,0.6);padding:30px 50px;border-radius:20px;pointer-events:none;opacity:0;transition:opacity 0.3s;z-index:50;';
|
|
406
|
+
container.appendChild(indicator);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
indicator.textContent = symbol;
|
|
410
|
+
indicator.style.opacity = '1';
|
|
411
|
+
|
|
412
|
+
// Hide after 1 second
|
|
413
|
+
setTimeout(function () {
|
|
414
|
+
indicator.style.opacity = '0';
|
|
415
|
+
}, 1000);
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
handleSettingsNavigation: function (key, event) {
|
|
420
|
+
var settingsBtn = document.getElementById('settingsBtn');
|
|
421
|
+
var settingsDropdown = document.getElementById('settingsDropdown');
|
|
422
|
+
var displayBtn = document.getElementById('displayBtn');
|
|
423
|
+
var logLevelSelect = document.getElementById('logLevelSelect');
|
|
424
|
+
|
|
425
|
+
switch (key) {
|
|
426
|
+
case 'ArrowDown':
|
|
427
|
+
// If dropdown is open and has select, focus it
|
|
428
|
+
if (settingsDropdown && settingsDropdown.style.display === 'block' && logLevelSelect) {
|
|
429
|
+
logLevelSelect.focus();
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
// Otherwise go to categories
|
|
433
|
+
if (settingsDropdown) settingsDropdown.style.display = 'none';
|
|
434
|
+
this.setZone('categories');
|
|
435
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
436
|
+
return true;
|
|
437
|
+
|
|
438
|
+
case 'ArrowLeft':
|
|
439
|
+
// Move to display button
|
|
440
|
+
if (settingsDropdown) settingsDropdown.style.display = 'none';
|
|
441
|
+
if (displayBtn) {
|
|
442
|
+
this.setZone('display');
|
|
443
|
+
displayBtn.focus();
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
|
|
447
|
+
case 'ArrowRight':
|
|
448
|
+
// Go to posts
|
|
449
|
+
if (settingsDropdown) settingsDropdown.style.display = 'none';
|
|
450
|
+
this.setZone('posts');
|
|
451
|
+
this.focusPost(this.focusedPostIndex);
|
|
452
|
+
return true;
|
|
453
|
+
|
|
454
|
+
case 'Escape':
|
|
455
|
+
case 'Back':
|
|
456
|
+
case 'Backspace':
|
|
457
|
+
// Close dropdown and go to categories
|
|
458
|
+
if (settingsDropdown) settingsDropdown.style.display = 'none';
|
|
459
|
+
this.setZone('categories');
|
|
460
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
461
|
+
return true;
|
|
462
|
+
|
|
463
|
+
case 'Enter':
|
|
464
|
+
// Toggle settings dropdown
|
|
465
|
+
if (settingsBtn) {
|
|
466
|
+
settingsBtn.click();
|
|
467
|
+
}
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
handleDisplayNavigation: function (key, event) {
|
|
474
|
+
var displayBtn = document.getElementById('displayBtn');
|
|
475
|
+
var displayPopup = document.getElementById('displayPopup');
|
|
476
|
+
var slider = document.getElementById('iconsPerRowSlider');
|
|
477
|
+
var settingsBtn = document.getElementById('settingsBtn');
|
|
478
|
+
|
|
479
|
+
switch (key) {
|
|
480
|
+
case 'ArrowDown':
|
|
481
|
+
// Close popup and go to categories
|
|
482
|
+
if (displayPopup) displayPopup.style.display = 'none';
|
|
483
|
+
this.setZone('categories');
|
|
484
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
485
|
+
return true;
|
|
486
|
+
|
|
487
|
+
case 'ArrowUp':
|
|
488
|
+
// Stay on slider if popup is open, otherwise do nothing
|
|
489
|
+
return true;
|
|
490
|
+
|
|
491
|
+
case 'ArrowLeft':
|
|
492
|
+
// Move to proxy indicator
|
|
493
|
+
if (displayPopup) displayPopup.style.display = 'none';
|
|
494
|
+
var proxyIndicator = document.getElementById('proxyStatusIndicator');
|
|
495
|
+
if (proxyIndicator) {
|
|
496
|
+
this.setZone('header');
|
|
497
|
+
proxyIndicator.focus();
|
|
498
|
+
}
|
|
499
|
+
return true;
|
|
500
|
+
|
|
501
|
+
case 'ArrowRight':
|
|
502
|
+
// Move to settings button
|
|
503
|
+
if (displayPopup) displayPopup.style.display = 'none';
|
|
504
|
+
if (settingsBtn) {
|
|
505
|
+
this.setZone('settings');
|
|
506
|
+
settingsBtn.focus();
|
|
507
|
+
}
|
|
508
|
+
return true;
|
|
509
|
+
|
|
510
|
+
case 'Escape':
|
|
511
|
+
case 'Back':
|
|
512
|
+
case 'Backspace':
|
|
513
|
+
// Close popup and go to categories
|
|
514
|
+
if (displayPopup) displayPopup.style.display = 'none';
|
|
515
|
+
this.setZone('categories');
|
|
516
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
517
|
+
return true;
|
|
518
|
+
|
|
519
|
+
case 'Enter':
|
|
520
|
+
// Toggle display popup
|
|
521
|
+
if (displayBtn) {
|
|
522
|
+
displayBtn.click();
|
|
523
|
+
}
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
handleHeaderNavigation: function (key, event) {
|
|
530
|
+
var proxyIndicator = document.getElementById('proxyStatusIndicator');
|
|
531
|
+
var displayBtn = document.getElementById('displayBtn');
|
|
532
|
+
|
|
533
|
+
switch (key) {
|
|
534
|
+
case 'ArrowDown':
|
|
535
|
+
// Go to categories
|
|
536
|
+
this.setZone('categories');
|
|
537
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
538
|
+
return true;
|
|
539
|
+
|
|
540
|
+
case 'ArrowUp':
|
|
541
|
+
// Stay at proxy indicator
|
|
542
|
+
return true;
|
|
543
|
+
|
|
544
|
+
case 'ArrowRight':
|
|
545
|
+
// Move to display button
|
|
546
|
+
if (displayBtn) {
|
|
547
|
+
this.setZone('display');
|
|
548
|
+
displayBtn.focus();
|
|
549
|
+
}
|
|
550
|
+
return true;
|
|
551
|
+
|
|
552
|
+
case 'ArrowLeft':
|
|
553
|
+
// Stay at proxy indicator (leftmost header item)
|
|
554
|
+
return true;
|
|
555
|
+
|
|
556
|
+
case 'Escape':
|
|
557
|
+
case 'Back':
|
|
558
|
+
case 'Backspace':
|
|
559
|
+
// Go to categories
|
|
560
|
+
this.setZone('categories');
|
|
561
|
+
this.focusCategory(this.focusedCategoryIndex);
|
|
562
|
+
return true;
|
|
563
|
+
|
|
564
|
+
case 'Enter':
|
|
565
|
+
// Trigger proxy status check
|
|
566
|
+
if (proxyIndicator) {
|
|
567
|
+
proxyIndicator.click();
|
|
568
|
+
}
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
calculateGridColumns: function () {
|
|
575
|
+
var savedIconsPerRow = localStorage.getItem('iconsPerRow');
|
|
576
|
+
if (savedIconsPerRow) {
|
|
577
|
+
this.gridColumns = parseInt(savedIconsPerRow, 10);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
var width = window.innerWidth;
|
|
582
|
+
if (width > 1400) {
|
|
583
|
+
this.gridColumns = 4;
|
|
584
|
+
} else if (width > 1000) {
|
|
585
|
+
this.gridColumns = 3;
|
|
586
|
+
} else if (width > 600) {
|
|
587
|
+
this.gridColumns = 2;
|
|
588
|
+
} else {
|
|
589
|
+
this.gridColumns = 1;
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
focusCategory: function (index) {
|
|
594
|
+
var categories = document.querySelectorAll('.category');
|
|
595
|
+
if (categories[index]) {
|
|
596
|
+
categories[index].focus();
|
|
597
|
+
this.scrollIntoViewIfNeeded(categories[index]);
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
focusPost: function (index) {
|
|
602
|
+
var posts = document.querySelectorAll('.post');
|
|
603
|
+
if (posts[index]) {
|
|
604
|
+
posts[index].focus();
|
|
605
|
+
this.scrollIntoViewIfNeeded(posts[index]);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
scrollIntoViewIfNeeded: function (element) {
|
|
610
|
+
if (!element) return;
|
|
611
|
+
|
|
612
|
+
var rect = element.getBoundingClientRect();
|
|
613
|
+
var isVisible = (
|
|
614
|
+
rect.top >= 0 &&
|
|
615
|
+
rect.left >= 0 &&
|
|
616
|
+
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
617
|
+
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
if (!isVisible) {
|
|
621
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
|
|
625
|
+
setZone: function (zone) {
|
|
626
|
+
console.log('DPadNavigator: Zone changed from ' + this.currentZone + ' to ' + zone);
|
|
627
|
+
this.currentZone = zone;
|
|
628
|
+
},
|
|
629
|
+
|
|
630
|
+
enterPlayerMode: function () {
|
|
631
|
+
var self = this;
|
|
632
|
+
console.log('DPadNavigator: Entering player mode');
|
|
633
|
+
|
|
634
|
+
// Set transition flag to block input
|
|
635
|
+
this.isTransitioning = true;
|
|
636
|
+
|
|
637
|
+
this.setZone('player');
|
|
638
|
+
this.focusedPlayerControlIndex = 1; // Start at video element (index 1), not close button
|
|
639
|
+
|
|
640
|
+
// Focus video element after transition delay
|
|
641
|
+
setTimeout(function () {
|
|
642
|
+
var video = document.getElementById('videoPlayer');
|
|
643
|
+
if (video) {
|
|
644
|
+
video.focus();
|
|
645
|
+
console.log('DPadNavigator: Focused video element');
|
|
646
|
+
}
|
|
647
|
+
// Clear transition flag after focus is set
|
|
648
|
+
self.isTransitioning = false;
|
|
649
|
+
}, 300); // Delay to avoid key repeat issues
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
exitPlayerMode: function () {
|
|
653
|
+
console.log('DPadNavigator: Exiting player mode');
|
|
654
|
+
this.setZone('posts');
|
|
655
|
+
// Restore focus to last selected post
|
|
656
|
+
this.focusPost(this.lastFocusedPostIndex);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// Expose to global scope
|
|
661
|
+
window.DPadNavigator = DPadNavigator;
|
|
662
|
+
|
|
663
|
+
})(window);
|