From edcd7cb148c27fb3e8a2e5da5a22eddcaecede19 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 8 Feb 2026 19:18:56 +0100 Subject: [PATCH] feat: improve global favorites handling and channel display logic in the UI --- .../favorites-layout.component.ts | 2 + libs/m3u-state/src/lib/effects.ts | 4 ++ .../m3u-utils/src/lib/playlist.utils.ts | 1 + .../channel-list-container.component.html | 6 +-- .../channel-list-container.component.ts | 43 ++++++++++++------- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts index 4750186c0..7e164f09f 100644 --- a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts +++ b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.ts @@ -1,4 +1,5 @@ import { Component, input, output } from '@angular/core'; +import { MatIconButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { TranslatePipe } from '@ngx-translate/core'; import { PlaylistSwitcherComponent, ResizableDirective } from 'components'; @@ -16,6 +17,7 @@ import { ContentCardComponent } from '../content-card/content-card.component'; CategoryViewComponent, ContentCardComponent, MatIcon, + MatIconButton, PlaylistSwitcherComponent, ResizableDirective, TranslatePipe, diff --git a/libs/m3u-state/src/lib/effects.ts b/libs/m3u-state/src/lib/effects.ts index 7f3610ebf..7a7097930 100644 --- a/libs/m3u-state/src/lib/effects.ts +++ b/libs/m3u-state/src/lib/effects.ts @@ -57,6 +57,10 @@ export class PlaylistEffects { this.store.select(selectFavorites), this.store.select(selectActivePlaylistId) ), + filter( + ([, , playlistId]) => + playlistId !== GLOBAL_FAVORITES_PLAYLIST_ID + ), switchMap(([, favorites, playlistId]) => this.playlistsService.updateFavorites(playlistId, favorites) ), diff --git a/libs/shared/m3u-utils/src/lib/playlist.utils.ts b/libs/shared/m3u-utils/src/lib/playlist.utils.ts index 7a7c7127f..ff82c3899 100644 --- a/libs/shared/m3u-utils/src/lib/playlist.utils.ts +++ b/libs/shared/m3u-utils/src/lib/playlist.utils.ts @@ -44,6 +44,7 @@ export function createFavoritesPlaylist( playlist: { items: channels, }, + favorites: channels.map((channel) => channel.url), filename: 'Global favorites', }; } diff --git a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html index 068b9c9b7..d165d02f5 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html +++ b/libs/ui/components/src/lib/channel-list-container/channel-list-container.component.html @@ -1,4 +1,4 @@ -@if (_channelList) { +@if (displayedChannels().length) { @@ -9,7 +9,7 @@ ()); @@ -72,6 +75,7 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { /** Channels array */ _channelList: Channel[] = []; + private readonly channelListSignal = signal([]); private channelList$ = new BehaviorSubject([]); get channelList(): Channel[] { @@ -81,13 +85,28 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { @Input() set channelList(value: Channel[]) { this._channelList = value; + this.channelListSignal.set(value); this.channelList$.next(value); - this.groupedChannels = _.default.groupBy(value, 'group.title'); this.fetchEpgForChannels(value); } + /** Active playlist ID as signal */ + private readonly activePlaylistIdSignal = + this.store.selectSignal(selectActivePlaylistId); + + /** Displayed channels - filters out unfavorited channels in global favorites view */ + readonly displayedChannels = computed(() => { + const channels = this.channelListSignal(); + if (this.activePlaylistIdSignal() === GLOBAL_FAVORITES_PLAYLIST_ID) { + return channels.filter((ch) => this.favoriteIds().has(ch.url)); + } + return channels; + }); + /** Object with channels sorted by groups */ - groupedChannels: { [key: string]: Channel[] } = {}; + readonly groupedChannels = computed(() => + _.default.groupBy(this.displayedChannels(), 'group.title') + ); /** Selected channel */ readonly activeChannel = this.store.selectSignal(selectActive); @@ -105,7 +124,8 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { ); /** Set of favorite channel URLs for quick lookup */ - readonly favoriteIds = signal>(new Set()); + private readonly _favorites = this.store.selectSignal(selectFavorites); + readonly favoriteIds = computed(() => new Set(this._favorites())); /** List with favorites */ favorites$ = combineLatest([ @@ -113,8 +133,6 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { this.channelList$, ]).pipe( map(([favoriteChannelIds, channelList]) => { - // Update the favoriteIds signal for quick lookup - this.favoriteIds.set(new Set(favoriteChannelIds)); return favoriteChannelIds .map((favoriteChannelId) => channelList.find( @@ -205,11 +223,6 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy { */ onFavoriteToggled(event: { channel: Channel; event: MouseEvent }): void { event.event.stopPropagation(); - this.snackBar.open( - this.translateService.instant('CHANNELS.FAVORITES_UPDATED'), - undefined, - { duration: 2000 } - ); this.store.dispatch( FavoritesActions.updateFavorites({ channel: event.channel }) );