mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat(epg): epg program list with date navigation
This commit is contained in:
4 files changed
+147
-76
No files matched your search
@@ -1,49 +1,87 @@
|
||||
<ng-container *ngIf="channel; else noEpg">
|
||||
<div fxFlex="80px" id="channel-info" fxLayoutAlign="row" fxLayoutGap="10px">
|
||||
<div fxLayoutAlign="column" fxLayoutGap="10px">
|
||||
<div fxFlex="48px" fxFlexAlign="center">
|
||||
<!-- channel info panel -->
|
||||
<div fxFlex="120px" id="channel-info" fxLayout="column">
|
||||
<div fxFlex="79px" fxLayout="row" fxLayoutGap="10px">
|
||||
<div
|
||||
fxFlex="60px"
|
||||
class="channel-icon"
|
||||
fxLayoutAlign="center center"
|
||||
>
|
||||
<img *ngIf="channel?.icon" [src]="channel.icon" width="48" />
|
||||
</div>
|
||||
<div fxFlex="100">
|
||||
<div class="channel-name">{{ channel?.name[0]?.value }}</div>
|
||||
<ng-container>
|
||||
<div class="playing-now">Playing now:</div>
|
||||
<div class="epg-program-name">
|
||||
{{
|
||||
playingNow
|
||||
? playingNow.title[0]?.value
|
||||
: 'Live stream'
|
||||
}}
|
||||
</div>
|
||||
</ng-container>
|
||||
<div fxFlex="100" fxFlexAlign="center">
|
||||
<div class="channel-name">
|
||||
{{ channel?.name[0]?.value }}
|
||||
</div>
|
||||
<div class="program-name">
|
||||
{{
|
||||
playingNow ? playingNow.title[0]?.value : 'Live stream'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex="40px" fxLayout="row" id="date-navigator">
|
||||
<button
|
||||
fxFlex="40px"
|
||||
mat-icon-button
|
||||
(click)="changeDate('prev')"
|
||||
matTooltip="Previous day"
|
||||
>
|
||||
<mat-icon>chevron_left</mat-icon>
|
||||
</button>
|
||||
<span
|
||||
class="selected-date"
|
||||
fxFlex="100"
|
||||
fxLayoutAlign="center center"
|
||||
>
|
||||
{{ dateToday | momentDate: 'YYYYMMDD':'MMMM Do dddd' }}</span
|
||||
>
|
||||
<button
|
||||
fxFlex="40px"
|
||||
mat-icon-button
|
||||
(click)="changeDate('next')"
|
||||
matTooltip="Next day"
|
||||
>
|
||||
<mat-icon>chevron_right</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- program list -->
|
||||
<mat-selection-list fxFlex [multiple]="false">
|
||||
<mat-list-option
|
||||
*ngFor="let item of items"
|
||||
[value]="item"
|
||||
[class.active]="timeNow >= item.start && timeNow <= item.stop"
|
||||
(click)="
|
||||
setEpgProgram(
|
||||
item,
|
||||
timeNow >= item.start && timeNow <= item.stop
|
||||
)
|
||||
"
|
||||
>
|
||||
<div matLine>
|
||||
<span class="time">{{ item.start }} - {{ item.stop }}</span>
|
||||
<span
|
||||
*ngIf="timeNow >= item.start && timeNow <= item.stop"
|
||||
class="live-badge"
|
||||
>live</span
|
||||
>
|
||||
</div>
|
||||
<p matLine>{{ item?.title[0]?.value }}</p>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-option>
|
||||
<ng-container *ngIf="items?.length > 0; else epgNotAvailable">
|
||||
<mat-list-option
|
||||
*ngFor="let item of items"
|
||||
[value]="item"
|
||||
[class.active]="timeNow >= item.start && timeNow <= item.stop"
|
||||
(click)="
|
||||
setEpgProgram(
|
||||
item,
|
||||
timeNow >= item.start && timeNow <= item.stop
|
||||
)
|
||||
"
|
||||
>
|
||||
<div matLine>
|
||||
<span class="time">{{ item.start }} - {{ item.stop }}</span>
|
||||
<span
|
||||
*ngIf="timeNow >= item.start && timeNow <= item.stop"
|
||||
class="live-badge"
|
||||
>live</span
|
||||
>
|
||||
</div>
|
||||
<p matLine>{{ item?.title[0]?.value }}</p>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-option>
|
||||
</ng-container>
|
||||
<ng-template #epgNotAvailable>
|
||||
<mat-list-option
|
||||
>Ooops, EPG is not available for the selected
|
||||
date</mat-list-option
|
||||
>
|
||||
</ng-template>
|
||||
</mat-selection-list>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #noEpg>
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
.active {
|
||||
border-bottom: 1px dashed #666;
|
||||
background: linear-gradient(to top, #d2d2d2 0%, #d6d6d6 20%, rgba(255, 255, 255, 0) 100%)
|
||||
|
||||
}
|
||||
|
||||
.time, .playing-now {
|
||||
.time {
|
||||
color: #666;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 0.8em;
|
||||
@@ -23,29 +22,27 @@
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.epg-program-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#channel-info {
|
||||
padding:10px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
font-weight: 800;
|
||||
}
|
||||
.channel-icon {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
mat-list {
|
||||
height: calc(100vh - 110px);
|
||||
overflow: auto;
|
||||
.channel-name {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.program-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
mat-selection-list {
|
||||
@@ -60,4 +57,13 @@ mat-selection-list {
|
||||
text-transform: uppercase;
|
||||
margin-left: 6px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
#date-navigator {
|
||||
border-top: 1px solid #999;
|
||||
|
||||
.selected-date {
|
||||
font-size: 0.9em;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { EpgListComponent, EpgData } from './epg-list.component';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MockModule } from 'ng-mocks';
|
||||
import { MockModule, MockPipe } from 'ng-mocks';
|
||||
import { ElectronService } from '../../../services/electron.service';
|
||||
import { ElectronServiceStub } from '../../../home/home.component.spec';
|
||||
import * as moment from 'moment';
|
||||
import { EPG_GET_PROGRAM_DONE } from '../../../shared/ipc-commands';
|
||||
import { ChannelStore } from '../../../state';
|
||||
import { MomentDatePipe } from '../../../shared/pipes/moment-date.pipe';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
describe('EpgListComponent', () => {
|
||||
let component: EpgListComponent;
|
||||
@@ -77,8 +79,8 @@ describe('EpgListComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EpgListComponent],
|
||||
imports: [MockModule(MatListModule)],
|
||||
declarations: [EpgListComponent, MockPipe(MomentDatePipe)],
|
||||
imports: [MockModule(MatIconModule), MockModule(MatListModule)],
|
||||
providers: [
|
||||
{ provide: ElectronService, useClass: ElectronServiceStub },
|
||||
],
|
||||
|
||||
@@ -37,6 +37,8 @@ export class EpgListComponent {
|
||||
/** Current time as formatted string */
|
||||
timeNow: string;
|
||||
|
||||
selectedDate: string;
|
||||
|
||||
/**
|
||||
* Creates an instance of EpgListComponent
|
||||
* @param electronService
|
||||
@@ -61,25 +63,11 @@ export class EpgListComponent {
|
||||
*/
|
||||
handleEpgData(programs: { payload: EpgData }): void {
|
||||
if (programs.payload?.items?.length > 0) {
|
||||
this.programs = programs;
|
||||
this.timeNow = moment(Date.now()).format('HH:mm');
|
||||
this.dateToday = moment(Date.now()).format('YYYYMMDD');
|
||||
this.channel = programs.payload?.channel;
|
||||
this.items = programs.payload?.items
|
||||
.filter((item) =>
|
||||
item.start.includes(this.dateToday.toString())
|
||||
)
|
||||
.map((program) => ({
|
||||
...program,
|
||||
start: moment(program.start, 'YYYYMMDDHHmm ZZ').format(
|
||||
'HH:mm'
|
||||
),
|
||||
stop: moment(program.stop, 'YYYYMMDDHHmm ZZ').format(
|
||||
'HH:mm'
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
return a.start.localeCompare(b.start);
|
||||
});
|
||||
this.items = this.selectPrograms(programs);
|
||||
|
||||
this.setPlayingNow();
|
||||
} else {
|
||||
@@ -88,6 +76,42 @@ export class EpgListComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the program based on the active date
|
||||
* @param programs object with all available epg programs for the active channel
|
||||
*/
|
||||
selectPrograms(programs: { payload: EpgData }): EpgProgram[] {
|
||||
return programs.payload?.items
|
||||
.filter((item) => item.start.includes(this.dateToday.toString()))
|
||||
.map((program) => ({
|
||||
...program,
|
||||
start: moment(program.start, 'YYYYMMDDHHmm ZZ').format('HH:mm'),
|
||||
stop: moment(program.stop, 'YYYYMMDDHHmm ZZ').format('HH:mm'),
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
return a.start.localeCompare(b.start);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the date to update the epg list with programs
|
||||
* @param direction direction to switch
|
||||
*/
|
||||
changeDate(direction: 'next' | 'prev'): void {
|
||||
let dateToSwitch;
|
||||
if (direction === 'next') {
|
||||
dateToSwitch = moment(this.dateToday, 'YYYYMMDD')
|
||||
.add(1, 'days')
|
||||
.format('YYYYMMDD');
|
||||
} else if (direction === 'prev') {
|
||||
dateToSwitch = moment(this.dateToday, 'YYYYMMDD')
|
||||
.subtract(1, 'days')
|
||||
.format('YYYYMMDD');
|
||||
}
|
||||
this.dateToday = dateToSwitch;
|
||||
this.items = this.selectPrograms(this.programs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playing now variable based on the current time
|
||||
*/
|
||||
@@ -99,7 +123,8 @@ export class EpgListComponent {
|
||||
|
||||
/**
|
||||
* Sets the provided epg program as active and starts to play
|
||||
* @param program
|
||||
* @param program epg program to set
|
||||
* @param isLive live stream flag
|
||||
*/
|
||||
setEpgProgram(program: EpgProgram, isLive?: boolean): void {
|
||||
isLive
|
||||
|
||||
Reference in new issue
Block a user