refactor: move favorites tab to own component

This commit is contained in:
4gray committed 2026-02-01 20:22:46 +01:00
1 parent 40173e6498
commit 3988708ab5
3 files changed
+173

No files matched your search

@@ -0,0 +1,41 @@
<div
class="channel-list"
cdkDropList
(cdkDropListDropped)="onDrop($event)"
id="favorites-list"
>
@if (enrichedFavorites.length > 0) {
@for (
channel of enrichedFavorites;
track trackByFn($index, channel);
let i = $index
) {
<app-channel-list-item
[name]="
i +
1 +
'. ' +
(channel?.name ||
'CHANNELS.UNNAMED_CHANNEL'
| translate)
"
[isDraggable]="true"
[logo]="channel?.tvg?.logo"
[showEpg]="shouldShowEpg()"
[epgProgram]="channel.epgProgram"
[progressPercentage]="channel.progressPercentage"
(clicked)="onChannelClick(channel)"
[selected]="activeChannelUrl() === channel?.url"
[showFavoriteButton]="true"
(favoriteToggled)="onFavoriteToggle(channel, $event)"
/>
}
} @else {
<div class="empty-favorites">
<strong>{{ 'CHANNELS.NO_FAVORITES' | translate }}</strong>
</div>
<div class="empty-favorites-hint">
{{ 'CHANNELS.USE_STAR_TO_FAVORITE' | translate }}
</div>
}
</div>
@@ -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;
}
@@ -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<Channel[]>();
/** EPG map for channel enrichment */
readonly channelEpgMap = input.required<Map<string, EpgProgram | null>>();
/** Progress tick to trigger progress recalculation */
readonly progressTick = input.required<number>();
/** Whether to show EPG data */
readonly shouldShowEpg = input.required<boolean>();
/** Currently active channel URL */
readonly activeChannelUrl = input<string | undefined>();
/** Emits when a channel is selected */
readonly channelSelected = output<Channel>();
/** Emits when favorite is toggled (removed) */
readonly favoriteToggled = output<{ channel: Channel; event: MouseEvent }>();
/** Emits when favorites order changes via drag-drop */
readonly favoritesReordered = output<string[]>();
/**
* 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<Channel[]>): void {
const favorites = [...this.favorites()];
moveItemInArray(favorites, event.previousIndex, event.currentIndex);
this.favoritesReordered.emit(favorites.map(item => item.url));
}
}