refactor: optimize progress calculation and enhance enriched channel retrieval in favorites and groups tabs

This commit is contained in:
4gray committed 2026-02-01 22:35:10 +01:00
1 parent bffcc24f67
commit bb1f3af082
5 files changed
+70 -22

No files matched your search

@@ -139,14 +139,26 @@ export class AllChannelsTabComponent implements OnDestroy {
return 0;
}
const now = new Date().getTime();
const now = Date.now();
const start = new Date(epgProgram.start).getTime();
const stop = new Date(epgProgram.stop).getTime();
const total = stop - start;
const elapsed = now - start;
// Validate start/stop are finite numbers
if (!Number.isFinite(start) || !Number.isFinite(stop)) {
return 0;
}
return Math.min(100, Math.max(0, (elapsed / total) * 100));
const total = stop - start;
// Bail out if duration is zero or negative
if (total <= 0) {
return 0;
}
// Clamp elapsed to [0, total]
const elapsed = Math.min(total, Math.max(0, now - start));
return Math.round((elapsed / total) * 100);
}
trackByFn(_: number, channel: Channel): string {
@@ -4,9 +4,9 @@
(cdkDropListDropped)="onDrop($event)"
id="favorites-list"
>
@if (enrichedFavorites.length > 0) {
@if (enrichedFavorites().length > 0) {
@for (
channel of enrichedFavorites;
channel of enrichedFavorites();
track trackByFn($index, channel);
let i = $index
) {
@@ -6,24 +6,21 @@ import {
import {
ChangeDetectionStrategy,
Component,
computed,
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';
import { ChannelListItemComponent } from '../channel-list-item/channel-list-item.component';
@Component({
selector: 'app-favorites-tab',
templateUrl: './favorites-tab.component.html',
styleUrls: ['./favorites-tab.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ChannelListItemComponent,
DragDropModule,
TranslatePipe,
],
imports: [ChannelListItemComponent, DragDropModule, TranslatePipe],
})
export class FavoritesTabComponent {
/** Favorite channels */
@@ -45,21 +42,24 @@ export class FavoritesTabComponent {
readonly channelSelected = output<Channel>();
/** Emits when favorite is toggled (removed) */
readonly favoriteToggled = output<{ channel: Channel; event: MouseEvent }>();
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
* Computed signal for enriched favorites with EPG data
*/
get enrichedFavorites(): EnrichedChannel[] {
readonly enrichedFavorites = computed(() => {
const favorites = this.favorites();
const epgMap = this.channelEpgMap();
// Read progressTick to trigger recalculation
this.progressTick();
return favorites.map(channel => {
return favorites.map((channel) => {
const channelId = channel?.tvg?.id?.trim() || channel?.name?.trim();
const epgProgram = channelId ? epgMap.get(channelId) : null;
return {
@@ -68,12 +68,14 @@ export class FavoritesTabComponent {
progressPercentage: this.calculateProgress(epgProgram),
} as EnrichedChannel;
});
}
});
/**
* Calculates progress percentage for an EPG program
*/
private calculateProgress(epgProgram: EpgProgram | null | undefined): number {
private calculateProgress(
epgProgram: EpgProgram | null | undefined
): number {
if (!epgProgram) {
return 0;
}
@@ -103,6 +105,6 @@ export class FavoritesTabComponent {
onDrop(event: CdkDragDrop<Channel[]>): void {
const favorites = [...this.favorites()];
moveItemInArray(favorites, event.previousIndex, event.currentIndex);
this.favoritesReordered.emit(favorites.map(item => item.url));
this.favoritesReordered.emit(favorites.map((item) => item.url));
}
}
@@ -20,7 +20,7 @@
<ng-template matExpansionPanelContent>
@let enrichedGroupChannels =
getEnrichedGroupChannels(groups.value);
getEnrichedGroupChannels(groups.value, groups.key);
@let limit = getGroupLimit(groups.key);
<div class="group-channels">
@for (
@@ -4,6 +4,7 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
inject,
input,
NgZone,
@@ -176,13 +177,46 @@ export class GroupsTabComponent implements AfterViewInit, OnDestroy {
}
/**
* Gets enriched channels for a specific group
* Computed signal that memoizes enriched channels for all groups
*/
getEnrichedGroupChannels(channels: Channel[]): EnrichedChannel[] {
private readonly enrichedGroupChannelsMap = computed(() => {
const grouped = this.groupedChannels();
const epgMap = this.channelEpgMap();
// Read progressTick to create dependency for progress refresh
this.progressTick();
const result = new Map<string, EnrichedChannel[]>();
for (const [groupKey, channels] of Object.entries(grouped)) {
result.set(
groupKey,
channels.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;
})
);
}
return result;
});
/**
* Gets enriched channels for a specific group from the memoized map
*/
getEnrichedGroupChannels(
channels: Channel[],
groupKey?: string
): EnrichedChannel[] {
// If groupKey provided, use memoized map
if (groupKey !== undefined) {
return this.enrichedGroupChannelsMap().get(groupKey) || [];
}
// Fallback for direct channel array (shouldn't happen with proper template usage)
const epgMap = this.channelEpgMap();
return channels.map((channel) => {
const channelId = channel?.tvg?.id?.trim() || channel?.name?.trim();
const epgProgram = channelId ? epgMap.get(channelId) : null;