From b1a873edaa4cb8b62e4df33082a84c59f8339ca5 Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 26 Mar 2026 19:26:01 +0100 Subject: [PATCH] feat: update EPG item description layout and increase dialog width for better visibility Entire-Checkpoint: 56d3e3141f61 --- .../epg-item-description.component.scss | 2 +- .../epg-list-item/epg-list-item.component.ts | 1 + .../src/lib/epg-list/epg-list.component.html | 39 ++------ .../src/lib/epg-list/epg-list.component.scss | 95 ++----------------- .../src/lib/epg-list/epg-list.component.ts | 51 +--------- .../multi-epg-container.component.ts | 38 +++++--- 6 files changed, 47 insertions(+), 179 deletions(-) diff --git a/libs/ui/epg/src/lib/epg-list/epg-item-description/epg-item-description.component.scss b/libs/ui/epg/src/lib/epg-list/epg-item-description/epg-item-description.component.scss index 2cf554704..1fbdcf550 100644 --- a/libs/ui/epg/src/lib/epg-list/epg-item-description/epg-item-description.component.scss +++ b/libs/ui/epg/src/lib/epg-list/epg-item-description/epg-item-description.component.scss @@ -13,7 +13,7 @@ $border-subtle: rgba(255, 255, 255, 0.08); background: linear-gradient(180deg, $bg-surface 0%, $bg-deep 100%); color: $text-primary; min-width: 380px; - max-width: 480px; + max-width: 800px; border-radius: 16px; overflow: hidden; position: relative; diff --git a/libs/ui/epg/src/lib/epg-list/epg-list-item/epg-list-item.component.ts b/libs/ui/epg/src/lib/epg-list/epg-list-item/epg-list-item.component.ts index 2e53fa099..705f4e35b 100644 --- a/libs/ui/epg/src/lib/epg-list/epg-list-item/epg-list-item.component.ts +++ b/libs/ui/epg/src/lib/epg-list/epg-list-item/epg-list-item.component.ts @@ -31,6 +31,7 @@ export class EpgListItemComponent { */ showDescription(program: EpgProgram): void { this.dialog.open(EpgItemDescriptionComponent, { + width: '800px', data: program, }); } diff --git a/libs/ui/epg/src/lib/epg-list/epg-list.component.html b/libs/ui/epg/src/lib/epg-list/epg-list.component.html index b72043b33..06612168c 100644 --- a/libs/ui/epg/src/lib/epg-list/epg-list.component.html +++ b/libs/ui/epg/src/lib/epg-list/epg-list.component.html @@ -3,34 +3,6 @@
-
- -
-
- {{ getChannelDisplayName(channel) }} -
-
- {{ - playingNow - ? (playingNow.title || 'EPG.LIVE_STREAM' | translate) - : ('EPG.LIVE_STREAM' | translate) - }} -
-
-
-
+
+ {{ program?.desc }} +
@if (isProgramPlaying(program)) {
@@ -119,7 +96,9 @@
diff --git a/libs/ui/epg/src/lib/epg-list/epg-list.component.scss b/libs/ui/epg/src/lib/epg-list/epg-list.component.scss index 2277c2367..4ba1c56e9 100644 --- a/libs/ui/epg/src/lib/epg-list/epg-list.component.scss +++ b/libs/ui/epg/src/lib/epg-list/epg-list.component.scss @@ -8,91 +8,6 @@ background: transparent; } -.header-divider { - height: 1px; - background: rgba(255, 255, 255, 0.08); -} - -#channel-header { - display: flex; - flex-direction: column; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0)), - rgba(127, 127, 127, 0.05); - border-bottom: 1px solid rgba(255, 255, 255, 0.06); - - .channel-info { - display: flex; - align-items: center; - padding: 14px 16px 12px; - gap: 14px; - } -} - -.channel-icon-shell { - position: relative; - width: 52px; - height: 52px; - flex-shrink: 0; - border-radius: 12px; - overflow: hidden; - display: grid; - place-items: center; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0)), - rgba(127, 127, 127, 0.12); - box-shadow: - inset 0 0 0 1px rgba(255, 255, 255, 0.08), - inset 0 1px 1px rgba(0, 0, 0, 0.15); - - img { - position: relative; - z-index: 1; - width: 100%; - height: 100%; - object-fit: contain; - padding: 6px; - box-sizing: border-box; - } -} - -.channel-icon-fallback { - position: absolute; - inset: 0; - margin: auto; - width: 20px; - height: 20px; - font-size: 20px; - opacity: 0.46; -} - -.channel-details { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 3px; -} - -.channel-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 1rem; - line-height: 1.2; - font-weight: 600; - letter-spacing: 0.01em; -} - -.program-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.84rem; - line-height: 1.2; - opacity: 0.82; -} - #date-navigator { display: flex; justify-content: space-between; @@ -206,6 +121,16 @@ -webkit-box-orient: vertical; } +.epg-item__description { + font-size: 0.82rem; + line-height: 1.4; + opacity: 0.74; + display: -webkit-box; + overflow: hidden; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + .program-progress { padding-top: 2px; } diff --git a/libs/ui/epg/src/lib/epg-list/epg-list.component.ts b/libs/ui/epg/src/lib/epg-list/epg-list.component.ts index 714ef7105..20a431785 100644 --- a/libs/ui/epg/src/lib/epg-list/epg-list.component.ts +++ b/libs/ui/epg/src/lib/epg-list/epg-list.component.ts @@ -3,8 +3,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ElementRef, effect, + ElementRef, inject, Injector, OnInit, @@ -14,14 +14,11 @@ import { FormsModule } from '@angular/forms'; import { MatIconButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; +import { EpgService } from '@iptvnator/epg/data-access'; import { MomentDatePipe } from '@iptvnator/pipes'; import { Store } from '@ngrx/store'; import { TranslatePipe } from '@ngx-translate/core'; -import { EpgService } from '@iptvnator/epg/data-access'; -import { - selectActive, - EpgActions, -} from 'm3u-state'; +import { EpgActions, selectActive } from 'm3u-state'; import moment from 'moment'; import { BehaviorSubject, combineLatest, map } from 'rxjs'; import { EpgChannel, EpgProgram } from 'shared-interfaces'; @@ -73,8 +70,6 @@ export class EpgListComponent implements OnInit { /** EPG selected program */ playingNow: EpgProgram | undefined; - channelIconFailed = false; - /** Selected date */ selectedDate!: string; @@ -96,7 +91,6 @@ export class EpgListComponent implements OnInit { url: active?.url ? [active.url] : [], icon: icons, }; - this.channelIconFailed = false; return ( active?.tvg?.rec || active?.timeshift || active?.catchup?.days ); @@ -125,45 +119,6 @@ export class EpgListComponent implements OnInit { ) ); - /** - * Helper function to get channel display name - */ - getChannelDisplayName(channel: EpgChannel): string { - if ( - !channel || - !channel.displayName || - channel.displayName.length === 0 - ) { - return ''; - } - // Return first available display name - return channel.displayName[0]?.value || ''; - } - - /** - * Helper function to get channel icon - */ - getChannelIcon(channel: EpgChannel): string { - if (!channel || !channel.icon || channel.icon.length === 0) { - return ''; - } - // Return first available icon src - return channel.icon[0]?.src || ''; - } - - showChannelIconFallback(channel: EpgChannel): boolean { - return !this.getChannelIcon(channel) || this.channelIconFailed; - } - - onChannelIconError(event: Event): void { - this.channelIconFailed = true; - (event.target as HTMLImageElement | null)?.style.setProperty( - 'display', - 'none' - ); - this.cdr.markForCheck(); - } - /** * Subscribe for values from the store on component init */ diff --git a/libs/ui/epg/src/lib/multi-epg/multi-epg-container.component.ts b/libs/ui/epg/src/lib/multi-epg/multi-epg-container.component.ts index 71a91f423..283d03ad4 100644 --- a/libs/ui/epg/src/lib/multi-epg/multi-epg-container.component.ts +++ b/libs/ui/epg/src/lib/multi-epg/multi-epg-container.component.ts @@ -54,12 +54,12 @@ interface ProgramSearchResult extends EpgProgram { @Component({ imports: [ - MatButtonModule, - MatIcon, - MatTooltip, - MomentDatePipe, - TranslatePipe -], + MatButtonModule, + MatIcon, + MatTooltip, + MomentDatePipe, + TranslatePipe, + ], selector: 'app-multi-epg-container', templateUrl: './multi-epg-container.component.html', styleUrls: ['./multi-epg-container.component.scss'], @@ -117,7 +117,7 @@ export class MultiEpgContainerComponent return allChannels; } - return allChannels.filter(channel => { + return allChannels.filter((channel) => { const name = this.getChannelName(channel).toLowerCase(); return name.includes(filter); }); @@ -151,7 +151,7 @@ export class MultiEpgContainerComponent this.interval = setInterval(() => { // Force recomputation by updating hourWidth to same value // This triggers the computed signal to recalculate - this.hourWidth.update(v => v); + this.hourWidth.update((v) => v); }, 60000); } @@ -317,7 +317,10 @@ export class MultiEpgContainerComponent if (typeof channel.displayName === 'string') { return channel.displayName; } - if (Array.isArray(channel.displayName) && channel.displayName.length > 0) { + if ( + Array.isArray(channel.displayName) && + channel.displayName.length > 0 + ) { return channel.displayName[0].value; } return ''; @@ -338,16 +341,16 @@ export class MultiEpgContainerComponent zoomIn(): void { if (this.hourWidth() >= 800) return; - this.hourWidth.update(v => v + 50); + this.hourWidth.update((v) => v + 50); } zoomOut(): void { if (this.hourWidth() <= 50) return; - this.hourWidth.update(v => v - 50); + this.hourWidth.update((v) => v - 50); } toggleSearch(): void { - this.isSearchExpanded.update(v => !v); + this.isSearchExpanded.update((v) => !v); if (!this.isSearchExpanded()) { this.channelFilter.set(''); } @@ -373,13 +376,14 @@ export class MultiEpgContainerComponent showDescription(program: EpgProgram): void { this.dialog.open(EpgItemDescriptionComponent, { + width: '600px', data: program, }); } // Program search methods toggleProgramSearch(): void { - this.isProgramSearchOpen.update(v => !v); + this.isProgramSearchOpen.update((v) => !v); if (!this.isProgramSearchOpen()) { this.programSearchQuery.set(''); this.programSearchResults.set([]); @@ -406,7 +410,10 @@ export class MultiEpgContainerComponent this.searchDebounceTimer = setTimeout(async () => { this.isSearchingPrograms.set(true); try { - const results = await window.electron.searchEpgPrograms(query, 20); + const results = await window.electron.searchEpgPrograms( + query, + 20 + ); this.programSearchResults.set( (results as ProgramSearchResult[]) || [] ); @@ -429,11 +436,12 @@ export class MultiEpgContainerComponent const channelId = program.channel_id || program.channel; // Find channel name - const channel = this.channels().find(ch => ch.id === channelId); + const channel = this.channels().find((ch) => ch.id === channelId); const channelName = channel ? this.getChannelName(channel) : null; // Open the program details dialog with channel name this.dialog.open(EpgItemDescriptionComponent, { + width: '800px', data: { ...program, channelName }, }); }