Implemented unique renderId for channel navigation
This commit is contained in:
@@ -20,6 +20,7 @@ class ChannelList {
|
|||||||
this.currentChannel = null;
|
this.currentChannel = null;
|
||||||
this.sources = [];
|
this.sources = [];
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
this.renderedChannels = [];
|
||||||
|
|
||||||
this.loadCollapsedState();
|
this.loadCollapsedState();
|
||||||
this.init();
|
this.init();
|
||||||
@@ -256,6 +257,28 @@ class ChannelList {
|
|||||||
this.groupedChannels = groupedChannels;
|
this.groupedChannels = groupedChannels;
|
||||||
this.showHidden = showHidden;
|
this.showHidden = showHidden;
|
||||||
|
|
||||||
|
// Build rendered channel list for navigation (matches visual order)
|
||||||
|
this.renderedChannels = [];
|
||||||
|
this.sortedGroups.forEach(groupName => {
|
||||||
|
const channels = this.groupedChannels[groupName];
|
||||||
|
const isFavoritesGroup = groupName === 'Favorites';
|
||||||
|
|
||||||
|
const visibleChannels = channels.filter(channel => {
|
||||||
|
if (isFavoritesGroup) return true;
|
||||||
|
const rawChannelId = channel.streamId || channel.id;
|
||||||
|
const channelHidden = this.isHidden('channel', channel.sourceId, rawChannelId);
|
||||||
|
return !channelHidden || this.showHidden;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assign unique render IDs for linear navigation
|
||||||
|
visibleChannels.forEach(ch => {
|
||||||
|
// We clone the object for the rendered list to attach the unique ID
|
||||||
|
// ensuring no side effects on the main channel object
|
||||||
|
const renderedCh = { ...ch, _renderId: `rid_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` };
|
||||||
|
this.renderedChannels.push(renderedCh);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Empty State
|
// Empty State
|
||||||
if (this.sortedGroups.length === 0) {
|
if (this.sortedGroups.length === 0) {
|
||||||
this.container.innerHTML = `
|
this.container.innerHTML = `
|
||||||
@@ -308,6 +331,8 @@ class ChannelList {
|
|||||||
this.loader.style.opacity = '1';
|
this.loader.style.opacity = '1';
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
|
let renderIndex = start; // Keep track of global index for mapping to renderedChannels
|
||||||
|
|
||||||
for (const groupName of groupsToRender) {
|
for (const groupName of groupsToRender) {
|
||||||
const channels = this.groupedChannels[groupName];
|
const channels = this.groupedChannels[groupName];
|
||||||
if (channels.length === 0) continue;
|
if (channels.length === 0) continue;
|
||||||
@@ -342,15 +367,21 @@ class ChannelList {
|
|||||||
const channelHidden = !isFavoritesGroup && this.isHidden('channel', channel.sourceId, rawChannelId);
|
const channelHidden = !isFavoritesGroup && this.isHidden('channel', channel.sourceId, rawChannelId);
|
||||||
|
|
||||||
const isActive = this.currentChannel?.id === channel.id;
|
const isActive = this.currentChannel?.id === channel.id;
|
||||||
|
// Check if this specific instance is the "active" one for navigation purposes
|
||||||
|
const isRenderActive = this.currentRenderId && this.renderedChannels[renderIndex]?._renderId === this.currentRenderId;
|
||||||
|
|
||||||
const isFavorite = this.isFavorite(channel.sourceId, channel.id);
|
const isFavorite = this.isFavorite(channel.sourceId, channel.id);
|
||||||
|
const renderId = this.renderedChannels[renderIndex]?._renderId || '';
|
||||||
|
renderIndex++;
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="channel-item ${isActive ? 'active' : ''} ${channelHidden ? 'hidden' : ''}"
|
<div class="channel-item ${isActive ? 'active' : ''} ${isRenderActive ? 'nav-active' : ''} ${channelHidden ? 'hidden' : ''}"
|
||||||
data-channel-id="${channel.id}"
|
data-channel-id="${channel.id}"
|
||||||
data-source-id="${channel.sourceId}"
|
data-source-id="${channel.sourceId}"
|
||||||
data-source-type="${channel.sourceType}"
|
data-source-type="${channel.sourceType}"
|
||||||
data-stream-id="${channel.streamId || ''}"
|
data-stream-id="${channel.streamId || ''}"
|
||||||
data-url="${channel.url || ''}">
|
data-url="${channel.url || ''}"
|
||||||
|
data-render-id="${renderId}">
|
||||||
<img class="channel-logo" src="${channel.tvgLogo && channel.tvgLogo.length > 0 ? channel.tvgLogo : '/img/placeholder.png'}"
|
<img class="channel-logo" src="${channel.tvgLogo && channel.tvgLogo.length > 0 ? channel.tvgLogo : '/img/placeholder.png'}"
|
||||||
alt="" onerror="this.onerror=null;this.src='/img/placeholder.png'">
|
alt="" onerror="this.onerror=null;this.src='/img/placeholder.png'">
|
||||||
<div class="channel-info">
|
<div class="channel-info">
|
||||||
@@ -458,6 +489,7 @@ class ChannelList {
|
|||||||
async loadChannels() {
|
async loadChannels() {
|
||||||
if (this.isLoading) return;
|
if (this.isLoading) return;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
this.currentRenderId = null; // Reset render tracking
|
||||||
|
|
||||||
const sourceValue = this.sourceSelect.value;
|
const sourceValue = this.sourceSelect.value;
|
||||||
const self = this;
|
const self = this;
|
||||||
@@ -813,7 +845,8 @@ class ChannelList {
|
|||||||
// Attach listeners
|
// Attach listeners
|
||||||
div.addEventListener('click', (e) => {
|
div.addEventListener('click', (e) => {
|
||||||
if (e.target.closest('.favorite-btn')) return;
|
if (e.target.closest('.favorite-btn')) return;
|
||||||
this.selectChannel(div.dataset);
|
// Pass the render ID from the dataset
|
||||||
|
this.selectChannel({ ...div.dataset, renderId: div.dataset.renderId });
|
||||||
});
|
});
|
||||||
div.addEventListener('contextmenu', (e) => this.showContextMenu(e, 'channel', div.dataset));
|
div.addEventListener('contextmenu', (e) => this.showContextMenu(e, 'channel', div.dataset));
|
||||||
|
|
||||||
@@ -836,14 +869,35 @@ class ChannelList {
|
|||||||
if (!channel) return;
|
if (!channel) return;
|
||||||
|
|
||||||
this.currentChannel = channel;
|
this.currentChannel = channel;
|
||||||
|
this.currentRenderId = dataset.renderId; // Track which visual instance is active
|
||||||
|
|
||||||
// Update active state in DOM without full re-render
|
// Update active state in DOM
|
||||||
this.container.querySelectorAll('.channel-item.active').forEach(el => {
|
this.container.querySelectorAll('.channel-item.active').forEach(el => {
|
||||||
el.classList.remove('active');
|
el.classList.remove('active');
|
||||||
|
el.classList.remove('nav-active');
|
||||||
});
|
});
|
||||||
const activeItem = this.container.querySelector(`[data-channel-id="${channel.id}"]`);
|
|
||||||
|
// Try to find specific render instance first
|
||||||
|
let activeItem;
|
||||||
|
if (this.currentRenderId) {
|
||||||
|
activeItem = this.container.querySelector(`[data-render-id="${this.currentRenderId}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to ID match if render ID not found (e.g. initial load or not rendered yet)
|
||||||
|
if (!activeItem) {
|
||||||
|
activeItem = this.container.querySelector(`[data-channel-id="${channel.id}"]`);
|
||||||
|
// If we fell back, update currentRenderId to match what we found
|
||||||
|
if (activeItem) {
|
||||||
|
this.currentRenderId = activeItem.dataset.renderId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (activeItem) {
|
if (activeItem) {
|
||||||
activeItem.classList.add('active');
|
activeItem.classList.add('active');
|
||||||
|
activeItem.classList.add('nav-active'); // Add specific class for navigation tracking
|
||||||
|
|
||||||
|
// Ensure visible (scroll into view if needed)
|
||||||
|
activeItem.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get stream URL
|
// Get stream URL
|
||||||
@@ -1041,23 +1095,34 @@ class ChannelList {
|
|||||||
* Select next channel in the current list
|
* Select next channel in the current list
|
||||||
*/
|
*/
|
||||||
selectNextChannel() {
|
selectNextChannel() {
|
||||||
if (!this.currentChannel || !this.filteredChannels || this.filteredChannels.length === 0) return;
|
if (!this.currentChannel || !this.renderedChannels || this.renderedChannels.length === 0) return;
|
||||||
|
|
||||||
const currentIndex = this.filteredChannels.findIndex(c =>
|
let currentIndex = -1;
|
||||||
c.id === this.currentChannel.id && c.sourceId === this.currentChannel.sourceId
|
|
||||||
);
|
// Try to find by render ID first (strict visual order)
|
||||||
|
if (this.currentRenderId) {
|
||||||
|
currentIndex = this.renderedChannels.findIndex(c => c._renderId === this.currentRenderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Find first matching channel ID (if render ID lost or invalid)
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
currentIndex = this.renderedChannels.findIndex(c =>
|
||||||
|
c.id === this.currentChannel.id && c.sourceId === this.currentChannel.sourceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (currentIndex === -1) return;
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
const nextIndex = (currentIndex + 1) % this.filteredChannels.length;
|
const nextIndex = (currentIndex + 1) % this.renderedChannels.length;
|
||||||
const nextChannel = this.filteredChannels[nextIndex];
|
const nextChannel = this.renderedChannels[nextIndex];
|
||||||
|
|
||||||
this.selectChannel({
|
this.selectChannel({
|
||||||
channelId: nextChannel.id,
|
channelId: nextChannel.id,
|
||||||
sourceId: nextChannel.sourceId,
|
sourceId: nextChannel.sourceId,
|
||||||
sourceType: nextChannel.sourceType,
|
sourceType: nextChannel.sourceType,
|
||||||
streamId: nextChannel.streamId,
|
streamId: nextChannel.streamId,
|
||||||
url: nextChannel.url
|
url: nextChannel.url,
|
||||||
|
renderId: nextChannel._renderId // Pass the unique render ID
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1065,23 +1130,32 @@ class ChannelList {
|
|||||||
* Select previous channel in the current list
|
* Select previous channel in the current list
|
||||||
*/
|
*/
|
||||||
selectPrevChannel() {
|
selectPrevChannel() {
|
||||||
if (!this.currentChannel || !this.filteredChannels || this.filteredChannels.length === 0) return;
|
if (!this.currentChannel || !this.renderedChannels || this.renderedChannels.length === 0) return;
|
||||||
|
|
||||||
const currentIndex = this.filteredChannels.findIndex(c =>
|
let currentIndex = -1;
|
||||||
c.id === this.currentChannel.id && c.sourceId === this.currentChannel.sourceId
|
|
||||||
);
|
if (this.currentRenderId) {
|
||||||
|
currentIndex = this.renderedChannels.findIndex(c => c._renderId === this.currentRenderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
currentIndex = this.renderedChannels.findIndex(c =>
|
||||||
|
c.id === this.currentChannel.id && c.sourceId === this.currentChannel.sourceId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (currentIndex === -1) return;
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
const prevIndex = (currentIndex - 1 + this.filteredChannels.length) % this.filteredChannels.length;
|
const prevIndex = (currentIndex - 1 + this.renderedChannels.length) % this.renderedChannels.length;
|
||||||
const prevChannel = this.filteredChannels[prevIndex];
|
const prevChannel = this.renderedChannels[prevIndex];
|
||||||
|
|
||||||
this.selectChannel({
|
this.selectChannel({
|
||||||
channelId: prevChannel.id,
|
channelId: prevChannel.id,
|
||||||
sourceId: prevChannel.sourceId,
|
sourceId: prevChannel.sourceId,
|
||||||
sourceType: prevChannel.sourceType,
|
sourceType: prevChannel.sourceType,
|
||||||
streamId: prevChannel.streamId,
|
streamId: prevChannel.streamId,
|
||||||
url: prevChannel.url
|
url: prevChannel.url,
|
||||||
|
renderId: prevChannel._renderId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user