Merge pull request #666 from 4gray/feat-extract-channel-item-add-epg-dialog

feat(ui): extract channel list item template and add EPG dialog
This commit is contained in:
4gray authored and GitHub committed 2025-11-29 18:48:34 +01:00
commit c2b1220386
3 files changed
+128 -62

No files matched your search

@@ -0,0 +1,80 @@
<div
class="channel-list-item"
[class.compact]="!showEpg"
cdkDrag
[cdkDragDisabled]="!isDraggable"
cdkDragPreviewContainer="parent"
[class.active]="selected"
(click)="clicked.emit()"
data-test-id="channel-item"
>
@if (isDraggable) {
<mat-icon cdkDragHandle class="drag-icon">drag_indicator</mat-icon>
}
<div class="channel-content">
@if (logo) {
<img
class="channel-logo"
[src]="logo"
onerror="this.style.display='none'"
alt="Channel Logo"
/>
}
<div class="channel-details">
<div class="channel-name">{{ name }}</div>
@if (showEpg) {
<div class="epg-container">
<div class="epg-content">
@if (epgProgram) {
<div class="epg-title">
{{ epgProgram.title?.[0]?.value }}
</div>
<div class="epg-timeline">
<span class="epg-time"
>{{ formatTime(epgProgram.start) }} -
{{ formatTime(epgProgram.stop) }}</span
>
<div class="epg-progress-track">
<div
class="epg-progress-fill"
[ngStyle]="{
width: progressPercentage + '%',
}"
></div>
</div>
</div>
} @else {
<div class="epg-placeholder">
{{ 'EPG.NO_PROGRAM_INFO' | translate }}
</div>
}
</div>
@if (epgProgram) {
<button
mat-icon-button
class="program-info-button"
[matTooltip]="
'EPG.PROGRAM_DIALOG.SHOW_PROGRAM_DETAILS'
| translate
"
(click)="showProgramDescription(epgProgram, $event)"
>
<mat-icon>info</mat-icon>
</button>
}
</div>
}
</div>
</div>
@if (showFavoriteButton) {
<button
mat-icon-button
class="favorite-button"
color="primary"
[matTooltip]="'CHANNELS.REMOVE_FAVORITE' | translate"
(click)="favoriteToggled.emit($event)"
>
<mat-icon color="accent">star</mat-icon>
</button>
}
</div>
@@ -1,5 +1,6 @@
// Main channel list item container
.channel-list-item {
width: 360px;
display: flex;
align-items: center;
height: 68px;
@@ -75,6 +76,7 @@
gap: 2px;
flex: 1;
min-width: 0; // Allow text truncation
overflow: visible;
}
// Channel name
@@ -90,6 +92,25 @@
max-width: 100%;
}
// EPG container - holds EPG content and info button
.epg-container {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
overflow: visible;
}
// EPG content area
.epg-content {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
min-width: 0;
overflow: hidden; // Allow text truncation within content
}
// EPG title (program name)
.epg-title {
font-family:
@@ -168,3 +189,13 @@
.favorite-button {
flex-shrink: 0;
}
// Program info button
.program-info-button {
flex-shrink: 0;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
@@ -1,5 +1,5 @@
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgIf, NgStyle } from '@angular/common';
import { NgStyle } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@@ -14,87 +14,30 @@ import {
SimpleChanges,
} from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatIcon } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core';
import { EpgProgram } from 'shared-interfaces';
import { EpgItemDescriptionComponent } from '../../epg-list/epg-item-description/epg-item-description.component';
@Component({
selector: 'app-channel-list-item',
styleUrls: ['./channel-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div
class="channel-list-item"
[class.compact]="!showEpg"
cdkDrag
[cdkDragDisabled]="!isDraggable"
cdkDragPreviewContainer="parent"
[class.active]="selected"
(click)="clicked.emit()"
data-test-id="channel-item"
>
<mat-icon
*ngIf="isDraggable"
cdkDragHandle
class="drag-icon"
>drag_indicator</mat-icon
>
<div class="channel-content">
<img
class="channel-logo"
*ngIf="logo"
[src]="logo"
onerror="this.style.display='none'"
alt="Channel Logo"
/>
<div class="channel-details">
<div class="channel-name">{{ name }}</div>
@if (showEpg) {
@if (epgProgram) {
<div class="epg-title">
{{ epgProgram.title?.[0]?.value }}
</div>
<div class="epg-timeline">
<span class="epg-time">{{ formatTime(epgProgram.start) }} - {{ formatTime(epgProgram.stop) }}</span>
<div class="epg-progress-track">
<div
class="epg-progress-fill"
[ngStyle]="{ width: progressPercentage + '%' }"
></div>
</div>
</div>
} @else {
<div class="epg-placeholder">
{{ 'EPG.NO_PROGRAM_INFO' | translate }}
</div>
}
}
</div>
</div>
<button
*ngIf="showFavoriteButton"
mat-icon-button
class="favorite-button"
color="primary"
[matTooltip]="'CHANNELS.REMOVE_FAVORITE' | translate"
(click)="favoriteToggled.emit($event)"
>
<mat-icon color="accent">star</mat-icon>
</button>
</div>`,
templateUrl: './channel-list-item.component.html',
imports: [
DragDropModule,
MatIcon,
MatIconButton,
MatTooltip,
NgIf,
NgStyle,
TranslatePipe,
],
})
export class ChannelListItemComponent implements OnInit, OnChanges, OnDestroy {
private readonly cdr = inject(ChangeDetectorRef);
private readonly dialog = inject(MatDialog);
@Input() isDraggable = false;
@Input() logo!: string;
@@ -165,4 +108,16 @@ export class ChannelListItemComponent implements OnInit, OnChanges, OnDestroy {
hour12: false,
});
}
/**
* Opens the dialog with details about the current EPG program
* @param program EPG program to show details for
* @param event Mouse event to stop propagation
*/
showProgramDescription(program: EpgProgram, event: MouseEvent): void {
event.stopPropagation();
this.dialog.open(EpgItemDescriptionComponent, {
data: program,
});
}
}