mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
feat: add dialog with detailed epg description
This commit is contained in:
14 files changed
+376
-69
No files matched your search
+40
@@ -0,0 +1,40 @@
|
||||
<h2 mat-dialog-title>
|
||||
{{ 'EPG.PROGRAM_DIALOG.PROGRAM_DETAILS' | translate }}
|
||||
</h2>
|
||||
<mat-dialog-content class="mat-typography">
|
||||
<ng-container *ngIf="epgProgram?.title?.length > 0">
|
||||
<div class="subheading-2">
|
||||
{{ 'EPG.PROGRAM_DIALOG.TITLE' | translate }}
|
||||
</div>
|
||||
<p data-test="title">{{ epgProgram.title[0].value }}</p>
|
||||
<div class="subheading-2">
|
||||
{{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }}
|
||||
</div>
|
||||
<p data-test="lang">{{ epgProgram.title[0].lang }}</p>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="epgProgram?.category?.length > 0">
|
||||
<div class="subheading-2">
|
||||
{{ 'EPG.PROGRAM_DIALOG.CATEGORY' | translate }}
|
||||
</div>
|
||||
<p data-test="category">{{ epgProgram.category[0].value }}</p>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="epgProgram?.desc?.length > 0">
|
||||
<div class="subheading-2">
|
||||
{{ 'EPG.PROGRAM_DIALOG.DESCRIPTION' | translate }}
|
||||
</div>
|
||||
<p data-test="desc">{{ epgProgram.desc[0].value }}</p>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="epgProgram?.rating?.length > 0">
|
||||
<div class="subheading-2">
|
||||
{{ 'EPG.PROGRAM_DIALOG.PARENTAL_RATING_SYSTEM' | translate }}
|
||||
</div>
|
||||
<p data-test="rating">
|
||||
{{ epgProgram.rating[0].value }} [{{ epgProgram.rating[0].system }}]
|
||||
</p>
|
||||
</ng-container>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close cdkFocusInitial color="primary">
|
||||
{{ 'HOME.PLAYLISTS.INFO_DIALOG.CLOSE' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
.subheading-2 {
|
||||
color: #666;
|
||||
text-decoration: underline;
|
||||
text-transform: uppercase;
|
||||
font-weight: 900;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MockPipe, MockModule } from 'ng-mocks';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { EpgItemDescriptionComponent } from './epg-item-description.component';
|
||||
import { EpgProgram } from '../../../models/epg-program.model';
|
||||
|
||||
describe('EpgItemDescriptionComponent', () => {
|
||||
let component: EpgItemDescriptionComponent;
|
||||
let fixture: ComponentFixture<EpgItemDescriptionComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
EpgItemDescriptionComponent,
|
||||
MockPipe(TranslatePipe),
|
||||
],
|
||||
imports: [MockModule(MatDialogModule)],
|
||||
providers: [{ provide: MAT_DIALOG_DATA, useValue: {} }],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EpgItemDescriptionComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.epgProgram = {} as EpgProgram;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render epg details in the dialog', () => {
|
||||
component.epgProgram = {
|
||||
title: [{ value: 'TV Show 1', lang: 'ru' }],
|
||||
desc: [{ value: 'Highly interesting show about pets' }],
|
||||
category: [{ value: 'Fun' }],
|
||||
} as EpgProgram;
|
||||
fixture.detectChanges();
|
||||
const title = fixture.debugElement.query(By.css('[data-test="title"]'));
|
||||
expect(title.nativeNode.innerHTML).toContain(
|
||||
component.epgProgram.title[0].value
|
||||
);
|
||||
const category = fixture.debugElement.query(
|
||||
By.css('[data-test="category"]')
|
||||
);
|
||||
expect(category.nativeNode.innerHTML).toContain(
|
||||
component.epgProgram.category[0].value
|
||||
);
|
||||
const desc = fixture.debugElement.query(By.css('[data-test="desc"]'));
|
||||
expect(desc.nativeNode.innerHTML).toContain(
|
||||
component.epgProgram.desc[0].value
|
||||
);
|
||||
});
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { EpgProgram } from './../../../models/epg-program.model';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-epg-item-description',
|
||||
templateUrl: './epg-item-description.component.html',
|
||||
styleUrls: ['./epg-item-description.component.scss'],
|
||||
})
|
||||
export class EpgItemDescriptionComponent {
|
||||
/** EPG program object */
|
||||
epgProgram: EpgProgram;
|
||||
/**
|
||||
* Creates an instance of the component and injects the program of the clicked epg program
|
||||
* @param epgProgram epg program
|
||||
*/
|
||||
constructor(@Inject(MAT_DIALOG_DATA) epgProgram: EpgProgram) {
|
||||
this.epgProgram = epgProgram;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<span class="time"
|
||||
>{{ item.start | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' }}
|
||||
-
|
||||
{{ item.stop | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' }}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="timeNow >= item.start && timeNow <= item.stop"
|
||||
class="live badge"
|
||||
[matTooltip]="'EPG.LIVE_NOW' | translate"
|
||||
>live</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
item.start < timeNow &&
|
||||
item.stop < timeNow &&
|
||||
item.start > timeshiftUntil
|
||||
"
|
||||
class="rec badge"
|
||||
[matTooltip]="'EPG.TIMESHIFT_AVAILABLE' | translate"
|
||||
>r</span
|
||||
>
|
||||
<span
|
||||
(click)="$event.stopPropagation(); showDescription(item)"
|
||||
*ngIf="item.desc.length > 0"
|
||||
class="description-icon"
|
||||
[matTooltip]="'EPG.PROGRAM_DIALOG.SHOW_PROGRAM_DETAILS' | translate"
|
||||
><mat-icon>info</mat-icon></span
|
||||
>
|
||||
<mat-divider></mat-divider>
|
||||
@@ -0,0 +1,29 @@
|
||||
.time {
|
||||
color: #666;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 0.8em;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
border-radius: 2px;
|
||||
padding: 2px 6px;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
margin-left: 6px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.live {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.rec {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.description-icon {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MomentDatePipe } from './../../../../shared/pipes/moment-date.pipe';
|
||||
import { EpgProgram } from './../../../models/epg-program.model';
|
||||
import { MockModule, MockPipe } from 'ng-mocks';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { EpgListItemComponent } from './epg-list-item.component';
|
||||
import { EpgItemDescriptionComponent } from './../epg-item-description/epg-item-description.component';
|
||||
import * as moment from 'moment';
|
||||
|
||||
const EPG_PROGRAM_ITEM = {
|
||||
start: moment(Date.now()).format('YYYYMMDD'),
|
||||
stop: moment(Date.now()).format('YYYYMMDD'),
|
||||
channel: '12345',
|
||||
title: [{ lang: 'en', value: 'NOW on PBS' }],
|
||||
desc: [
|
||||
{
|
||||
lang: 'en',
|
||||
value:
|
||||
"Jordan's Queen Rania has made job creation a priority to help curb the staggering unemployment rates among youths in the Middle East.",
|
||||
},
|
||||
],
|
||||
date: ['20080711'],
|
||||
category: [
|
||||
{ lang: 'en', value: 'Newsmagazine' },
|
||||
{ lang: 'en', value: 'Interview' },
|
||||
],
|
||||
episodeNum: [
|
||||
{ system: 'dd_progid', value: 'EP01006886.0028' },
|
||||
{ system: 'onscreen', value: '427' },
|
||||
],
|
||||
previouslyShown: [{ start: '20080711000000' }],
|
||||
subtitles: [{ type: 'teletext' }],
|
||||
rating: [
|
||||
{
|
||||
system: 'VCHIP',
|
||||
value: 'TV-G',
|
||||
},
|
||||
],
|
||||
credits: [
|
||||
{
|
||||
role: 'actor',
|
||||
name: 'Peter Bergman',
|
||||
},
|
||||
],
|
||||
icon: [
|
||||
'http://imageswoapi.whatsonindia.com/WhatsOnTV/images/ProgramImages/xlarge/38B4DE4E9A7132257749051B6C8B4F699DB264F4V.jpg',
|
||||
],
|
||||
audio: [],
|
||||
_attributes: {
|
||||
start: moment(Date.now()).format('YYYYMMDD'),
|
||||
stop: moment(Date.now()).format('YYYYMMDD'),
|
||||
},
|
||||
};
|
||||
|
||||
describe('EpgListItemComponent', () => {
|
||||
let component: EpgListItemComponent;
|
||||
let fixture: ComponentFixture<EpgListItemComponent>;
|
||||
let dialog: MatDialog;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
EpgListItemComponent,
|
||||
MockPipe(MomentDatePipe),
|
||||
MockPipe(TranslatePipe),
|
||||
],
|
||||
imports: [
|
||||
MockModule(MatDialogModule),
|
||||
MockModule(MatListModule),
|
||||
MockModule(MatIconModule),
|
||||
MockModule(MatTooltipModule),
|
||||
],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EpgListItemComponent);
|
||||
dialog = TestBed.inject(MatDialog);
|
||||
component = fixture.componentInstance;
|
||||
component.item = EPG_PROGRAM_ITEM;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should trigger the function to open the details dialog', () => {
|
||||
spyOn(dialog, 'open');
|
||||
component.showDescription({} as EpgProgram);
|
||||
expect(dialog.open).toHaveBeenCalledTimes(1);
|
||||
expect(dialog.open).toHaveBeenCalledWith(EpgItemDescriptionComponent, {
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { EpgItemDescriptionComponent } from './../epg-item-description/epg-item-description.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { EpgProgram } from '../../../models/epg-program.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-epg-list-item',
|
||||
templateUrl: './epg-list-item.component.html',
|
||||
styleUrls: ['./epg-list-item.component.scss'],
|
||||
})
|
||||
export class EpgListItemComponent {
|
||||
/** EPG Program to render */
|
||||
@Input() item: EpgProgram;
|
||||
|
||||
/** Actual time */
|
||||
@Input() timeNow: string;
|
||||
|
||||
/** Aviability of the timeshift function until date */
|
||||
@Input() timeshiftUntil: string;
|
||||
|
||||
/**
|
||||
* Creates an instance of EpgListItemComponent
|
||||
* @param dialog angular material dialog
|
||||
*/
|
||||
constructor(private dialog: MatDialog) {}
|
||||
|
||||
/**
|
||||
* Opens the dialog with details about the selected program
|
||||
* @param program selected epg program
|
||||
*/
|
||||
showDescription(program: EpgProgram): void {
|
||||
this.dialog.open(EpgItemDescriptionComponent, {
|
||||
data: program,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -56,51 +56,47 @@
|
||||
*ngIf="timeshiftUntil$ | async as timeshiftUntil"
|
||||
>
|
||||
<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,
|
||||
item.start > timeshiftUntil
|
||||
)
|
||||
"
|
||||
[disabled]="
|
||||
item.start < timeshiftUntil || item.start >= timeNow
|
||||
"
|
||||
>
|
||||
<div matLine>
|
||||
<span class="time"
|
||||
>{{
|
||||
item.start | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm'
|
||||
}}
|
||||
-
|
||||
{{
|
||||
item.stop | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm'
|
||||
}}</span
|
||||
>
|
||||
<span
|
||||
*ngIf="timeNow >= item.start && timeNow <= item.stop"
|
||||
class="live badge"
|
||||
[matTooltip]="'EPG.LIVE_NOW' | translate"
|
||||
>live</span
|
||||
>
|
||||
<span
|
||||
*ngIf="
|
||||
item.start < timeNow &&
|
||||
item.stop < timeNow &&
|
||||
item.start > timeshiftUntil
|
||||
<ng-container *ngFor="let item of items">
|
||||
<mat-list-item
|
||||
*ngIf="
|
||||
item.start < timeshiftUntil || item.start >= timeNow;
|
||||
else timeshiftItem
|
||||
"
|
||||
>
|
||||
<div matLine>
|
||||
<app-epg-list-item
|
||||
[timeshiftUntil]="timeshiftUntil"
|
||||
[timeNow]="timeNow"
|
||||
[item]="item"
|
||||
></app-epg-list-item>
|
||||
</div>
|
||||
<p matLine>{{ item?.title[0]?.value }}</p>
|
||||
</mat-list-item>
|
||||
<ng-template #timeshiftItem>
|
||||
<mat-list-option
|
||||
[value]="item"
|
||||
[class.active]="
|
||||
timeNow >= item.start && timeNow <= item.stop
|
||||
"
|
||||
(click)="
|
||||
setEpgProgram(
|
||||
item,
|
||||
timeNow >= item.start && timeNow <= item.stop,
|
||||
item.start > timeshiftUntil
|
||||
)
|
||||
"
|
||||
class="rec badge"
|
||||
[matTooltip]="'EPG.TIMESHIFT_AVAILABLE' | translate"
|
||||
>r</span
|
||||
>
|
||||
</div>
|
||||
<p matLine>{{ item?.title[0]?.value }}</p>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-option>
|
||||
<div matLine>
|
||||
<app-epg-list-item
|
||||
[timeshiftUntil]="timeshiftUntil"
|
||||
[timeNow]="timeNow"
|
||||
[item]="item"
|
||||
></app-epg-list-item>
|
||||
</div>
|
||||
<p matLine>{{ item?.title[0]?.value }}</p>
|
||||
</mat-list-option>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #epgNotAvailable>
|
||||
<mat-list-option>{{
|
||||
|
||||
@@ -13,14 +13,6 @@
|
||||
border-bottom: 1px dashed #666;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #666;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 0.8em;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#channel-info {
|
||||
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);
|
||||
@@ -48,22 +40,6 @@ mat-selection-list {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.badge {
|
||||
border-radius: 2px;
|
||||
padding: 2px 6px;
|
||||
color: #fff;
|
||||
text-transform: uppercase;
|
||||
margin-left: 6px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.live {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.rec {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
#date-navigator {
|
||||
border-top: 1px solid #999;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { MockComponent } from 'ng-mocks';
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { EpgListComponent, EpgData } from './epg-list.component';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
@@ -12,12 +14,14 @@ import { EPG_GET_PROGRAM_DONE } from '../../../../../ipc-commands';
|
||||
import { Channel, ChannelStore } from '../../../state';
|
||||
import { MomentDatePipe } from '../../../shared/pipes/moment-date.pipe';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { EpgListItemComponent } from './epg-list-item/epg-list-item.component';
|
||||
|
||||
describe('EpgListComponent', () => {
|
||||
let component: EpgListComponent;
|
||||
let fixture: ComponentFixture<EpgListComponent>;
|
||||
let electronService: ElectronService;
|
||||
let channelStore: ChannelStore;
|
||||
let dialog: MatDialog;
|
||||
|
||||
const MOCKED_PROGRAMS = {
|
||||
channel: {
|
||||
@@ -90,11 +94,13 @@ describe('EpgListComponent', () => {
|
||||
EpgListComponent,
|
||||
MockPipe(MomentDatePipe),
|
||||
MockPipe(TranslatePipe),
|
||||
MockComponent(EpgListItemComponent),
|
||||
],
|
||||
imports: [
|
||||
MockModule(MatIconModule),
|
||||
MockModule(MatTooltipModule),
|
||||
MockModule(MatListModule),
|
||||
MockModule(MatDialogModule),
|
||||
],
|
||||
providers: [
|
||||
{ provide: ElectronService, useClass: ElectronServiceStub },
|
||||
@@ -107,6 +113,7 @@ describe('EpgListComponent', () => {
|
||||
fixture = TestBed.createComponent(EpgListComponent);
|
||||
component = fixture.componentInstance;
|
||||
electronService = TestBed.inject(ElectronService);
|
||||
dialog = TestBed.inject(MatDialog);
|
||||
channelStore = TestBed.inject(ChannelStore);
|
||||
channelStore.setActiveChannel(({
|
||||
id: '',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EpgListItemComponent } from './components/epg-list/epg-list-item/epg-list-item.component';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChannelListContainerComponent } from './components/channel-list-container/channel-list-container.component';
|
||||
@@ -7,6 +8,7 @@ import { VjsPlayerComponent } from './components/vjs-player/vjs-player.component
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { EpgListComponent } from './components/epg-list/epg-list.component';
|
||||
import { EpgItemDescriptionComponent } from './components/epg-list/epg-item-description/epg-item-description.component';
|
||||
|
||||
const routes: Routes = [{ path: '', component: VideoPlayerComponent }];
|
||||
|
||||
@@ -14,7 +16,9 @@ const routes: Routes = [{ path: '', component: VideoPlayerComponent }];
|
||||
imports: [CommonModule, RouterModule.forChild(routes), SharedModule],
|
||||
declarations: [
|
||||
ChannelListContainerComponent,
|
||||
EpgItemDescriptionComponent,
|
||||
EpgListComponent,
|
||||
EpgListItemComponent,
|
||||
HtmlVideoPlayerComponent,
|
||||
VideoPlayerComponent,
|
||||
VjsPlayerComponent,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"PARENTAL_RATING_SYSTEM": "Alterskennzeichnung (USK)",
|
||||
"DESCRIPTION": "Beschreibung",
|
||||
"LANGUAGE": "Sprache",
|
||||
"SHOW_PROGRAM_DETAILS": "Show details about this program"
|
||||
"SHOW_PROGRAM_DETAILS": "Details anzeigen"
|
||||
}
|
||||
},
|
||||
"HOME": {
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
import 'jest-extended';
|
||||
Reference in new issue
Block a user