From 96e93c5b0cf1b8d9e703d93c66a1ad552ab44ed8 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 12 Mar 2021 10:24:58 +0100 Subject: [PATCH] feat: add dialog with detailed epg description --- .../epg-item-description.component.html | 40 +++++++ .../epg-item-description.component.scss | 7 ++ .../epg-item-description.component.spec.ts | 59 ++++++++++ .../epg-item-description.component.ts | 20 ++++ .../epg-list-item.component.html | 29 +++++ .../epg-list-item.component.scss | 29 +++++ .../epg-list-item.component.spec.ts | 103 ++++++++++++++++++ .../epg-list-item/epg-list-item.component.ts | 36 ++++++ .../epg-list/epg-list.component.html | 82 +++++++------- .../epg-list/epg-list.component.scss | 24 ---- .../epg-list/epg-list.component.spec.ts | 9 +- src/app/player/player.module.ts | 4 + src/assets/i18n/de.json | 2 +- src/global.d.ts | 1 + 14 files changed, 376 insertions(+), 69 deletions(-) create mode 100644 src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html create mode 100644 src/app/player/components/epg-list/epg-item-description/epg-item-description.component.scss create mode 100644 src/app/player/components/epg-list/epg-item-description/epg-item-description.component.spec.ts create mode 100644 src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts create mode 100644 src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html create mode 100644 src/app/player/components/epg-list/epg-list-item/epg-list-item.component.scss create mode 100644 src/app/player/components/epg-list/epg-list-item/epg-list-item.component.spec.ts create mode 100644 src/app/player/components/epg-list/epg-list-item/epg-list-item.component.ts create mode 100644 src/global.d.ts diff --git a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html new file mode 100644 index 000000000..01493a03d --- /dev/null +++ b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.html @@ -0,0 +1,40 @@ +

+ {{ 'EPG.PROGRAM_DIALOG.PROGRAM_DETAILS' | translate }} +

+ + +
+ {{ 'EPG.PROGRAM_DIALOG.TITLE' | translate }} +
+

{{ epgProgram.title[0].value }}

+
+ {{ 'EPG.PROGRAM_DIALOG.LANGUAGE' | translate }} +
+

{{ epgProgram.title[0].lang }}

+
+ +
+ {{ 'EPG.PROGRAM_DIALOG.CATEGORY' | translate }} +
+

{{ epgProgram.category[0].value }}

+
+ +
+ {{ 'EPG.PROGRAM_DIALOG.DESCRIPTION' | translate }} +
+

{{ epgProgram.desc[0].value }}

+
+ +
+ {{ 'EPG.PROGRAM_DIALOG.PARENTAL_RATING_SYSTEM' | translate }} +
+

+ {{ epgProgram.rating[0].value }} [{{ epgProgram.rating[0].system }}] +

+
+
+ + + diff --git a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.scss b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.scss new file mode 100644 index 000000000..9af7320e7 --- /dev/null +++ b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.scss @@ -0,0 +1,7 @@ +.subheading-2 { + color: #666; + text-decoration: underline; + text-transform: uppercase; + font-weight: 900; + font-size: 0.8em; +} \ No newline at end of file diff --git a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.spec.ts b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.spec.ts new file mode 100644 index 000000000..594e62cc5 --- /dev/null +++ b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.spec.ts @@ -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; + + 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 + ); + }); +}); diff --git a/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts new file mode 100644 index 000000000..3dbcdd09d --- /dev/null +++ b/src/app/player/components/epg-list/epg-item-description/epg-item-description.component.ts @@ -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; + } +} diff --git a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html new file mode 100644 index 000000000..07a6e640b --- /dev/null +++ b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.html @@ -0,0 +1,29 @@ +{{ item.start | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' }} + - + {{ item.stop | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' }} +live +r +info + diff --git a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.scss b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.scss new file mode 100644 index 000000000..cc2568d05 --- /dev/null +++ b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.scss @@ -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; +} \ No newline at end of file diff --git a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.spec.ts b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.spec.ts new file mode 100644 index 000000000..4060e501b --- /dev/null +++ b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.spec.ts @@ -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; + 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: {}, + }); + }); +}); diff --git a/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.ts b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.ts new file mode 100644 index 000000000..86c408a49 --- /dev/null +++ b/src/app/player/components/epg-list/epg-list-item/epg-list-item.component.ts @@ -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, + }); + } +} diff --git a/src/app/player/components/epg-list/epg-list.component.html b/src/app/player/components/epg-list/epg-list.component.html index 1f3faea56..c3ad43b9e 100644 --- a/src/app/player/components/epg-list/epg-list.component.html +++ b/src/app/player/components/epg-list/epg-list.component.html @@ -56,51 +56,47 @@ *ngIf="timeshiftUntil$ | async as timeshiftUntil" > - -
- {{ - item.start | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' - }} - - - {{ - item.stop | momentDate: 'YYYYMMDDHHmm ZZ':'HH:mm' - }} - live - + +
+ +
+

{{ item?.title[0]?.value }}

+
+ + r
-
-

{{ item?.title[0]?.value }}

- -
+
+ +
+

{{ item?.title[0]?.value }}

+ + +
{{ diff --git a/src/app/player/components/epg-list/epg-list.component.scss b/src/app/player/components/epg-list/epg-list.component.scss index f3772a324..c4bcb3356 100644 --- a/src/app/player/components/epg-list/epg-list.component.scss +++ b/src/app/player/components/epg-list/epg-list.component.scss @@ -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; diff --git a/src/app/player/components/epg-list/epg-list.component.spec.ts b/src/app/player/components/epg-list/epg-list.component.spec.ts index e7a955ba7..a35a43e82 100644 --- a/src/app/player/components/epg-list/epg-list.component.spec.ts +++ b/src/app/player/components/epg-list/epg-list.component.spec.ts @@ -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; 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: '', diff --git a/src/app/player/player.module.ts b/src/app/player/player.module.ts index 560e436b4..c30102efe 100644 --- a/src/app/player/player.module.ts +++ b/src/app/player/player.module.ts @@ -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, diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index d45b72619..1d42fb397 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -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": { diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 000000000..9e6c1dbd9 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1 @@ +import 'jest-extended';