feat(ui): surface live language filters and built-in EPG sources (#23)

Add language filter controls for projected live channels, preserve backend language views in the browser, and separate built-in EPG sources from custom sources.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-06 11:42:23 +02:00
parent 2a1c745a26
commit 638a31d9ef
9 changed files with 425 additions and 25 deletions

View File

@ -12,9 +12,12 @@
const favorites = new Set();
const hiddenItems = new Set();
const channels = [];
const logicalChannels = [];
const hasLanguageViews = response.channels.every(channel => channel?.language && channel?.logicalChannelId);
response.channels.forEach(logicalChannel => {
if (!logicalChannel || !Array.isArray(logicalChannel.variants)) return;
const mappedVariants = [];
logicalChannel.variants.forEach(variant => {
const source = sourceById.get(String(variant.sourceId));
const id = String(variant.id);
@ -32,14 +35,44 @@
sourceType: variant.sourceType ?? source?.type ?? 'xtream',
sourceName: variant.sourceName ?? source?.name ?? ''
};
if (variant.language || logicalChannel.language) {
mapped.language = variant.language || logicalChannel.language;
}
channels.push(mapped);
mappedVariants.push(mapped);
const key = `${mapped.sourceId}:${id}`;
if (variant.isFavorite) favorites.add(key);
if (variant.isHidden) hiddenItems.add(`channel:${mapped.sourceId}:${mapped.streamId || id}`);
});
if (!mappedVariants.length) return;
const preferredVariant = mappedVariants.find(variant =>
`${variant.sourceId}:${variant.id}` === String(logicalChannel.selectedVariantId)
) || mappedVariants.find(variant => String(variant.id) === String(logicalChannel.preferredVariant?.id))
|| mappedVariants[0];
const language = logicalChannel.language || preferredVariant.language;
if (hasLanguageViews) logicalChannels.push({
...logicalChannel,
fingerprint: logicalChannel.id,
displayName: logicalChannel.displayName || logicalChannel.standardName || logicalChannel.name,
name: logicalChannel.displayName || logicalChannel.standardName || logicalChannel.name,
variants: mappedVariants,
visibleVariants: mappedVariants.filter(variant => !variant.isHidden),
preferredVariant,
language,
selectedVariantId: `${preferredVariant.sourceId}:${preferredVariant.id}`,
groupTitle: preferredVariant.groupTitle,
tvgLogo: preferredVariant.tvgLogo,
tvgId: logicalChannel.epg?.channelId || preferredVariant.tvgId,
isFavorite: mappedVariants.some(variant => variant.isFavorite),
isHidden: mappedVariants.every(variant => variant.isHidden)
});
});
return { channels, favorites, hiddenItems, projection: response.projection || {} };
return {
channels, logicalChannels: hasLanguageViews ? logicalChannels : null, favorites, hiddenItems,
availableLanguages: response.filters?.availableLanguages || [],
projection: response.projection || {}
};
}
function canUse(response) {