Skip to content

Commit

Permalink
web-app: Improvements to Program Guide page
Browse files Browse the repository at this point in the history
- Do not display question mark icons on channels that have no icon
- Add page-left (<<) and page-right (>>) links in the grid.
- Show program recording status in the grid.
- Show actual date in the grid, instead of "Today" always.
- If you mess up entering the date, go to current date rather than 1969.
  • Loading branch information
bennettpeter committed May 23, 2023
1 parent eaaa820 commit e5acde6
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 120 deletions.
30 changes: 30 additions & 0 deletions mythtv/html/assets/i18n/en_US.json
Expand Up @@ -1038,5 +1038,35 @@
"rolloff_label": "Roll-off",
"rolloff_desc": "Roll-off factor (Default: 0.35)"
}
},
"data": {
"recstatus": {
"Pending": "Pending",
"Failing": "Failing",
"MissedFuture": "Missed Future",
"Tuning": "Tuning",
"Failed": "Failed",
"TunerBusy": "Tuner Busy",
"LowDiskSpace": "Low Disk Space",
"Cancelled": "Cancelled",
"Missed": "Missed",
"Aborted": "Aborted",
"Recorded": "Recorded",
"Recording": "Recording",
"WillRecord": "Will Record",
"Unknown": "Unknown",
"DontRecord": "Dont Record",
"PreviousRecording": "Previous Recording",
"CurrentRecording": "Current Recording",
"EarlierShowing": "Earlier Showing",
"TooManyRecordings": "Too Many Recordings",
"NotListed": "Not Listed",
"Conflict": "Conflict",
"LaterShowing": "Later Showing",
"Repeat": "Repeat",
"Inactive": "Inactive",
"NeverRecord": "Never Record",
"Offline": "Offline"
}
}
}
2 changes: 2 additions & 0 deletions mythtv/html/backend/src/app/app.module.ts
Expand Up @@ -76,6 +76,7 @@ import { ChannelscanComponent } from './config/settings/input-connections/channe
import { DashboardRoutingModule } from './dashboard/dashboard-routing.module';
import { WizChanneleditComponent } from './config/setupwizard/wiz-channeledit/wiz-channeledit.component';
import { RecordingsComponent } from './dashboard/recordings/recordings.component';
import { ScheduleComponent } from './schedule/schedule.component';

// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
Expand Down Expand Up @@ -147,6 +148,7 @@ export function HttpLoaderFactory(http: HttpClient) {
ChannelscanComponent,
WizChanneleditComponent,
RecordingsComponent,
ScheduleComponent,
],
imports: [
BrowserModule,
Expand Down
@@ -1,7 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { MenuItem, Message, MessageService } from 'primeng/api';
import { MenuItem, MessageService } from 'primeng/api';
import { Menu } from 'primeng/menu';
import { PartialObserver } from 'rxjs';
import { DvrService } from 'src/app/services/dvr.service';
Expand All @@ -21,7 +21,7 @@ export class RecordingsComponent implements OnInit {
@ViewChild("menu") menu!: Menu;

recordings!: ProgramList;
program!: ScheduleOrProgram;
program: ScheduleOrProgram = <ScheduleOrProgram>{ Title: '' };
editingProgram?: ScheduleOrProgram;
displayMetadataDlg = false;
displayUnsaved = false;
Expand Down Expand Up @@ -59,8 +59,6 @@ export class RecordingsComponent implements OnInit {
this.translate.get(value).subscribe(data => {
Object.defineProperty(this.msg, key, { value: data });
});

this.program = <ScheduleOrProgram>{ Title: '' };
}

const mnu_entries = [this.mnu_delete, this.mnu_delete_rerec, this.mnu_undelete, this.mnu_rerec, this.mnu_markwatched,
Expand Down
@@ -1,8 +1,9 @@
<div class="flex flex-column align-items-center channelBox">
<div class="channelIcon">
<img alt="{{ channel.IconURL }} Icon" height="57" src="{{IconUrl(channel.IconURL)}}"/>
<img src="{{channel.IconURL}}" height="57" *ngIf="channel.IconURL; else nullIcon">
<ng-template #nullIcon><img height="0" width="0"></ng-template>
</div>
<div class="channelText">
<span>{{ channel.CallSign }}</span>
<span>{{ channel.ChanNum}} {{ channel.CallSign }}</span>
</div>
</div>
</div>
Expand Up @@ -14,11 +14,4 @@ export class ChannelIconComponent implements OnInit {
ngOnInit(): void {
}

IconUrl(url: string) : string {
if (url == null || url.length == 0) {
return "/images/tv/channel_icon.svg";
} else {
return url;
}
}
}
Expand Up @@ -43,7 +43,8 @@
font-weight: bold;
margin-right: 5px;
line-height: 15px;
padding-bottom: 5px;
padding-top: 2px;
padding-bottom: 3px;
}

.programDescription {
Expand Down
@@ -1,20 +1,21 @@
<div class="programBox" [style.width.%]=durationToWidth() (click)="showDetailsDialog()">
<div class="programBox cursor-pointer" [style.width.%]=durationToWidth() (click)="showDetailsDialog()">
<div class="programTitle">{{ program.Title }}</div>
<div class="programBody">
<div *ngIf="program.SubTitle.length != 0">
<div class="programSubtitle">{{ program.SubTitle }}</div>
</div>
<div *ngIf="program.Recording">
<div class="programSubtitle" [ngStyle]="['WillRecord','Recording'].indexOf(program.Recording.StatusName)>-1?
{'background-color':'palegreen'}:{'background-color':'pink'}">
{{ guide.recStatusText[program.Recording.StatusName] }}</div>
</div>
<div class="programDescription">{{ program.Description }}</div>
</div>
</div>
<p-dialog header=" {{ program.Title }}" [(visible)]="displayDetails" [style]="{width: '50vw'}">
<div class="programSubtitle">{{ toLocalShortTime(program.StartTime) }} - {{ toLocalShortTime(program.EndTime) }}
<ng-container *ngIf="program.SubTitle.length != 0">
: {{ program.SubTitle }}
</ng-container>
</div>
<p>{{ program.Description }}</p>
<p-dialog header=" {{ program.Title }} : {{ program.SubTitle }}" [(visible)]="editSchedule" [modal]="true"
[style]="{height: '75vw', width: '50vw'}" [closable]="false" [closeOnEscape]="false">
<app-schedule></app-schedule>
<ng-template pTemplate="footer">
<p-button icon="pi pi-check" (click)="displayDetails=false" label="Ok" styleClass="p-button-text"></p-button>
<p-button icon="pi pi-check" (click)="editSchedule=false" label="Ok" styleClass="p-button-text"></p-button>
</ng-template>
</p-dialog>
@@ -1,58 +1,43 @@
import { Component, Input, OnInit } from '@angular/core';
import { ScheduleOrProgram } from 'src/app/services/interfaces/program.interface';
import { GuideComponent } from '../../guide.component';

@Component({
selector: 'app-guide-programentry',
templateUrl: './programentry.component.html',
styleUrls: ['./programentry.component.css']
})
export class ProgramEntryComponent implements OnInit {
@Input() program! : ScheduleOrProgram;
@Input() guideStartTime! : string;
@Input() guideEndTime! : string;
displayDetails : boolean = false;
@Input() program!: ScheduleOrProgram;
@Input() guideStartTime!: string;
@Input() guideEndTime!: string;
@Input() guide!: GuideComponent;

constructor() { }
editSchedule: boolean = false;

constructor() {
}

ngOnInit(): void {
}

durationToWidth() : number {
durationToWidth(): number {
let p_start = new Date(this.program.StartTime);
let p_end = new Date(this.program.EndTime);
let p_end = new Date(this.program.EndTime);
let w_start = new Date(this.guideStartTime);
let w_end = new Date(this.guideEndTime);
let w_end = new Date(this.guideEndTime);
let beginsBefore = p_start < w_start;
let endsAfter = p_end > w_end;
let startPoint = (beginsBefore ? w_start : p_start);
let endPoint = (endsAfter ? w_end: p_end);
let endPoint = (endsAfter ? w_end : p_end);
let timeWindow = w_end.getTime() - w_start.getTime();
let programWindow = endPoint.getTime() - startPoint.getTime();
let program_width = ((programWindow / timeWindow)*100);
let program_width = ((programWindow / timeWindow) * 100);
return program_width;
}

// Taken from timebar.components.ts, but takes string arg, not Date. TODO: Refactor
toLocalShortTime(date : string) : string {
let d = new Date(date);
let ampm = 'AM';
let h;
let m = d.getMinutes().toString().padStart(2, '0');
let hour = d.getHours();
if (hour == 0) {
h = 12;
} else if (hour > 12) {
h = hour - 12;
} else {
h = hour;
}
if (hour >= 12) {
ampm = 'PM';
}
return h + ":" + m + " " + ampm;
}

showDetailsDialog() {
this.displayDetails = true;
this.editSchedule = true;
}
}
@@ -1,8 +1,19 @@
<div class="grid">
<!-- TODO: Use today or date-->
<div class="col-1 datebox">{{ 'primeng.today' | translate }}</div>
<div class="col segmentbox">{{ segmentToStartTime(0) }}</div>
<div class="col-1 datebox">{{ guide.m_startDate.toLocaleDateString() }}</div>
<div class="col segmentbox">
<div class="flex">
<div class="flex-none cursor-pointer" (click)="pageLeft()">&lt;&lt;</div>
<div class="flex-none">&nbsp;{{ segmentToStartTime(0) }}</div>
</div>
</div>
<div class="col segmentbox">{{ segmentToStartTime(1) }}</div>
<div class="col segmentbox">{{ segmentToStartTime(2) }}</div>
<div class="col segmentbox">{{ segmentToStartTime(3) }}</div>
<div class="col segmentbox">
<div class="flex">
<div class="flex-none">{{ segmentToStartTime(3) }}</div>
<div class="flex-grow-1">&nbsp;</div>
<div class="flex-none cursor-pointer" (click)="pageRight()">&gt;&gt;</div>
</div>
</div>
</div>
@@ -1,42 +1,38 @@
import { Component, OnInit, Input } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { GuideComponent } from '../../guide.component';

@Component({
selector: 'app-guide-timebar',
templateUrl: './timebar.component.html',
styleUrls: ['./timebar.component.css']
})
export class TimebarComponent implements OnInit {
@Input() startTime! : string;
@Input() guide!: GuideComponent;

constructor(private translate : TranslateService) { }
constructor(private translate: TranslateService) {
}

ngOnInit(): void {
}

toLocalShortTime(date : Date) : string {
let d = new Date(date);
let ampm = 'AM';
let h;
let m = d.getMinutes().toString().padStart(2, '0');
let hour = d.getHours();
if (hour == 0) {
h = 12;
} else if (hour > 12) {
h = hour - 12;
} else {
h = hour;
}
if (hour >= 12) {
ampm = 'PM';
}
return h + ":" + m + " " + ampm;
segmentToStartTime(segment: number) {
// let st = new Date(this.startTime);
const offset = segment * 1800000; /* 30 mins */
const t = new Date(this.guide.m_startDate.getTime() + offset);
// return this.toLocalShortTime(t);
// Get the locale specific time and remove the seconds
const tWithSecs = t.toLocaleTimeString() + ' ';
return tWithSecs.replace(/:.. /, ' ');
}

pageLeft() {
this.guide.m_pickerDate = new Date(this.guide.m_startDate.getTime() - 7200000);
this.guide.onDatePickerClose();
}

segmentToStartTime(segment : number) {
let st = new Date(this.startTime);
const offset = segment * 1800000; /* 30 mins */
const t = new Date(st.getTime() + offset);
return this.toLocalShortTime(t);
pageRight() {
this.guide.m_pickerDate = new Date(this.guide.m_startDate.getTime() + 7200000);
this.guide.onDatePickerClose();
}
}
30 changes: 18 additions & 12 deletions mythtv/html/backend/src/app/guide/guide.component.html
@@ -1,22 +1,26 @@
<h2>{{ 'dashboard.programguide' | translate }} </h2>
<div *ngIf="(m_guideData$ | async)?.ProgramGuide as pg; else loading">
<p-dataView [value]="pg.Channels" [totalRecords]="pg.TotalAvailable">
<!-- <div *ngIf="(m_guideData$ | async)?.ProgramGuide as pg; else loading"> -->
<div *ngIf="loaded; else loading">
<p-dataView [value]="m_programGuide.ProgramGuide.Channels"
[totalRecords]="m_programGuide.ProgramGuide.TotalAvailable">
<ng-template pTemplate="header" styleClass="timeHeader">
<div class="col-12">
<p-calendar
[(ngModel)]="m_startDate"
[showTime]="true"
[stepMinute]="30"
[dateFormat]=(m_dateFormat)
<div class="flex">
<div class="flex flex-initial">
<p-calendar [(ngModel)]="m_pickerDate" [showTime]="true" [stepMinute]="30" [dateFormat]=(m_dateFormat)
(onClose)="onDatePickerClose()">
</p-calendar>
</div>
<div class="flex flex-initial">
<div *ngIf="refreshing"><p-progressSpinner
[style]="{width: '30px', height: '30px'}"></p-progressSpinner></div>
</div>
</div>
</ng-template>
<ng-template let-channelrow let-i="rowIndex" pTemplate="listItem">
<div class="grid col-12 p-3">
<ng-container *ngIf="( i % 5 ) == 0">
<div class="col-12">
<app-guide-timebar [startTime]="pg.StartTime"></app-guide-timebar>
<app-guide-timebar [guide]="this"></app-guide-timebar>
</div>
</ng-container>
<div class="grid flex-grow-1">
Expand All @@ -25,9 +29,11 @@ <h2>{{ 'dashboard.programguide' | translate }} </h2>
</div>
<div class="col">
<ng-container *ngFor="let program of channelrow.Programs">
<ng-container *ngIf="inDisplayWindow(program.StartTime, program.EndTime)">
<ng-container *ngIf="!refreshing && inDisplayWindow(program.StartTime, program.EndTime)">
<app-guide-programentry [program]="program"
[guideStartTime]="pg.StartTime" [guideEndTime]="pg.EndTime">
[guideStartTime]="m_programGuide.ProgramGuide.StartTime"
[guideEndTime]="m_programGuide.ProgramGuide.EndTime"
[guide]="this">
</app-guide-programentry>
</ng-container>
</ng-container>
Expand All @@ -39,4 +45,4 @@ <h2>{{ 'dashboard.programguide' | translate }} </h2>
</div>
<ng-template #loading>
<p-progressSpinner></p-progressSpinner>
</ng-template>
</ng-template>

0 comments on commit e5acde6

Please sign in to comment.