feat(epg): epg program list with date navigation

This commit is contained in:
4gray committed 2021-02-05 00:09:00 +01:00
1 parent 4881ba8fe0
commit fdbe02b47e
4 files changed
+147 -76

No files matched your search

@@ -1,49 +1,87 @@
<ng-container *ngIf="channel; else noEpg"> <ng-container *ngIf="channel; else noEpg">
<div fxFlex="80px" id="channel-info" fxLayoutAlign="row" fxLayoutGap="10px"> <!-- channel info panel -->
<div fxLayoutAlign="column" fxLayoutGap="10px"> <div fxFlex="120px" id="channel-info" fxLayout="column">
<div fxFlex="48px" fxFlexAlign="center"> <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" /> <img *ngIf="channel?.icon" [src]="channel.icon" width="48" />
</div> </div>
<div fxFlex="100"> <div fxFlex="100" fxFlexAlign="center">
<div class="channel-name">{{ channel?.name[0]?.value }}</div> <div class="channel-name">
<ng-container> {{ channel?.name[0]?.value }}
<div class="playing-now">Playing now:</div> </div>
<div class="epg-program-name"> <div class="program-name">
{{ {{
playingNow playingNow ? playingNow.title[0]?.value : 'Live stream'
? playingNow.title[0]?.value }}
: 'Live stream' </div>
}}
</div>
</ng-container>
</div> </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> </div>
<!-- program list -->
<mat-selection-list fxFlex [multiple]="false"> <mat-selection-list fxFlex [multiple]="false">
<mat-list-option <ng-container *ngIf="items?.length > 0; else epgNotAvailable">
*ngFor="let item of items" <mat-list-option
[value]="item" *ngFor="let item of items"
[class.active]="timeNow >= item.start && timeNow <= item.stop" [value]="item"
(click)=" [class.active]="timeNow >= item.start && timeNow <= item.stop"
setEpgProgram( (click)="
item, setEpgProgram(
timeNow >= item.start && timeNow <= item.stop item,
) timeNow >= item.start && timeNow <= item.stop
" )
> "
<div matLine> >
<span class="time">{{ item.start }} - {{ item.stop }}</span> <div matLine>
<span <span class="time">{{ item.start }} - {{ item.stop }}</span>
*ngIf="timeNow >= item.start && timeNow <= item.stop" <span
class="live-badge" *ngIf="timeNow >= item.start && timeNow <= item.stop"
>live</span class="live-badge"
> >live</span
</div> >
<p matLine>{{ item?.title[0]?.value }}</p> </div>
<mat-divider></mat-divider> <p matLine>{{ item?.title[0]?.value }}</p>
</mat-list-option> <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> </mat-selection-list>
</ng-container> </ng-container>
<ng-template #noEpg> <ng-template #noEpg>
<mat-list> <mat-list>
<mat-list-item> <mat-list-item>
@@ -12,10 +12,9 @@
.active { .active {
border-bottom: 1px dashed #666; border-bottom: 1px dashed #666;
background: linear-gradient(to top, #d2d2d2 0%, #d6d6d6 20%, rgba(255, 255, 255, 0) 100%) background: linear-gradient(to top, #d2d2d2 0%, #d6d6d6 20%, rgba(255, 255, 255, 0) 100%)
} }
.time, .playing-now { .time {
color: #666; color: #666;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
font-size: 0.8em; font-size: 0.8em;
@@ -23,29 +22,27 @@
margin-bottom: 5px; 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 { #channel-info {
padding:10px;
border-bottom: 1px solid #ccc; 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); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
background-color: #ccc; background-color: #ccc;
}
.channel-name { .channel-icon {
font-weight: 800; overflow: hidden;
} }
mat-list { .channel-name {
height: calc(100vh - 110px); font-weight: 800;
overflow: auto; }
.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 { mat-selection-list {
@@ -61,3 +58,12 @@ mat-selection-list {
margin-left: 6px; margin-left: 6px;
font-size: 0.8em; 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EpgListComponent, EpgData } from './epg-list.component'; import { EpgListComponent, EpgData } from './epg-list.component';
import { MatListModule } from '@angular/material/list'; import { MatListModule } from '@angular/material/list';
import { MockModule } from 'ng-mocks'; import { MockModule, MockPipe } from 'ng-mocks';
import { ElectronService } from '../../../services/electron.service'; import { ElectronService } from '../../../services/electron.service';
import { ElectronServiceStub } from '../../../home/home.component.spec'; import { ElectronServiceStub } from '../../../home/home.component.spec';
import * as moment from 'moment'; import * as moment from 'moment';
import { EPG_GET_PROGRAM_DONE } from '../../../shared/ipc-commands'; import { EPG_GET_PROGRAM_DONE } from '../../../shared/ipc-commands';
import { ChannelStore } from '../../../state'; import { ChannelStore } from '../../../state';
import { MomentDatePipe } from '../../../shared/pipes/moment-date.pipe';
import { MatIconModule } from '@angular/material/icon';
describe('EpgListComponent', () => { describe('EpgListComponent', () => {
let component: EpgListComponent; let component: EpgListComponent;
@@ -77,8 +79,8 @@ describe('EpgListComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [EpgListComponent], declarations: [EpgListComponent, MockPipe(MomentDatePipe)],
imports: [MockModule(MatListModule)], imports: [MockModule(MatIconModule), MockModule(MatListModule)],
providers: [ providers: [
{ provide: ElectronService, useClass: ElectronServiceStub }, { provide: ElectronService, useClass: ElectronServiceStub },
], ],
@@ -37,6 +37,8 @@ export class EpgListComponent {
/** Current time as formatted string */ /** Current time as formatted string */
timeNow: string; timeNow: string;
selectedDate: string;
/** /**
* Creates an instance of EpgListComponent * Creates an instance of EpgListComponent
* @param electronService * @param electronService
@@ -61,25 +63,11 @@ export class EpgListComponent {
*/ */
handleEpgData(programs: { payload: EpgData }): void { handleEpgData(programs: { payload: EpgData }): void {
if (programs.payload?.items?.length > 0) { if (programs.payload?.items?.length > 0) {
this.programs = programs;
this.timeNow = moment(Date.now()).format('HH:mm'); this.timeNow = moment(Date.now()).format('HH:mm');
this.dateToday = moment(Date.now()).format('YYYYMMDD'); this.dateToday = moment(Date.now()).format('YYYYMMDD');
this.channel = programs.payload?.channel; this.channel = programs.payload?.channel;
this.items = programs.payload?.items this.items = this.selectPrograms(programs);
.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.setPlayingNow(); this.setPlayingNow();
} else { } 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 * 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 * 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 { setEpgProgram(program: EpgProgram, isLive?: boolean): void {
isLive isLive