From 3988708ab5d227f1fba26527673e4582ca9c2dee Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 1 Feb 2026 20:22:46 +0100 Subject: [PATCH] refactor: move favorites tab to own component --- .../favorites-tab.component.html | 41 +++++++ .../favorites-tab.component.scss | 24 ++++ .../favorites-tab/favorites-tab.component.ts | 108 ++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.html create mode 100644 libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.scss create mode 100644 libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.ts diff --git a/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.html b/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.html new file mode 100644 index 000000000..97861373b --- /dev/null +++ b/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.html @@ -0,0 +1,41 @@ +
+ @if (enrichedFavorites.length > 0) { + @for ( + channel of enrichedFavorites; + track trackByFn($index, channel); + let i = $index + ) { + + } + } @else { +
+ {{ 'CHANNELS.NO_FAVORITES' | translate }} +
+
+ {{ 'CHANNELS.USE_STAR_TO_FAVORITE' | translate }} +
+ } +
diff --git a/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.scss b/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.scss new file mode 100644 index 000000000..9cbd2f7b0 --- /dev/null +++ b/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.scss @@ -0,0 +1,24 @@ +#favorites-list { + height: calc(100vh - 105px); + overflow-x: hidden; +} + +.channel-list { + overflow-x: hidden; +} + +.empty-favorites, +.empty-favorites-hint { + padding: 16px 24px; + text-align: center; +} + +.empty-favorites { + margin-top: 40px; + font-size: 1rem; +} + +.empty-favorites-hint { + opacity: 0.7; + font-size: 0.9rem; +} diff --git a/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.ts b/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.ts new file mode 100644 index 000000000..c345f73b0 --- /dev/null +++ b/libs/ui/components/src/lib/channel-list-container/favorites-tab/favorites-tab.component.ts @@ -0,0 +1,108 @@ +import { + CdkDragDrop, + DragDropModule, + moveItemInArray, +} from '@angular/cdk/drag-drop'; +import { + ChangeDetectionStrategy, + Component, + input, + output, +} from '@angular/core'; +import { TranslatePipe } from '@ngx-translate/core'; +import { Channel, EpgProgram } from 'shared-interfaces'; +import { ChannelListItemComponent } from '../channel-list-item/channel-list-item.component'; +import { EnrichedChannel } from '../all-channels-tab/all-channels-tab.component'; + +@Component({ + selector: 'app-favorites-tab', + templateUrl: './favorites-tab.component.html', + styleUrls: ['./favorites-tab.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ChannelListItemComponent, + DragDropModule, + TranslatePipe, + ], +}) +export class FavoritesTabComponent { + /** Favorite channels */ + readonly favorites = input.required(); + + /** EPG map for channel enrichment */ + readonly channelEpgMap = input.required>(); + + /** Progress tick to trigger progress recalculation */ + readonly progressTick = input.required(); + + /** Whether to show EPG data */ + readonly shouldShowEpg = input.required(); + + /** Currently active channel URL */ + readonly activeChannelUrl = input(); + + /** Emits when a channel is selected */ + readonly channelSelected = output(); + + /** Emits when favorite is toggled (removed) */ + readonly favoriteToggled = output<{ channel: Channel; event: MouseEvent }>(); + + /** Emits when favorites order changes via drag-drop */ + readonly favoritesReordered = output(); + + /** + * Gets enriched favorites with EPG data + */ + get enrichedFavorites(): EnrichedChannel[] { + const favorites = this.favorites(); + const epgMap = this.channelEpgMap(); + // Read progressTick to trigger recalculation + this.progressTick(); + + return favorites.map(channel => { + const channelId = channel?.tvg?.id?.trim() || channel?.name?.trim(); + const epgProgram = channelId ? epgMap.get(channelId) : null; + return { + ...channel, + epgProgram, + progressPercentage: this.calculateProgress(epgProgram), + } as EnrichedChannel; + }); + } + + /** + * Calculates progress percentage for an EPG program + */ + private calculateProgress(epgProgram: EpgProgram | null | undefined): number { + if (!epgProgram) { + return 0; + } + + const now = new Date().getTime(); + const start = new Date(epgProgram.start).getTime(); + const stop = new Date(epgProgram.stop).getTime(); + + const total = stop - start; + const elapsed = now - start; + + return Math.min(100, Math.max(0, (elapsed / total) * 100)); + } + + trackByFn(_: number, channel: Channel): string { + return channel?.id; + } + + onChannelClick(channel: Channel): void { + this.channelSelected.emit(channel); + } + + onFavoriteToggle(channel: Channel, event: MouseEvent): void { + this.favoriteToggled.emit({ channel, event }); + } + + onDrop(event: CdkDragDrop): void { + const favorites = [...this.favorites()]; + moveItemInArray(favorites, event.previousIndex, event.currentIndex); + this.favoritesReordered.emit(favorites.map(item => item.url)); + } +}