Skip to content

Commit

Permalink
webapp setup: Channel Editor
Browse files Browse the repository at this point in the history
This is the channel editor part of setup. Editing of channels is done.
The transport editor part is not dome yet.
  • Loading branch information
bennettpeter committed Feb 22, 2023
1 parent 293adf2 commit 91e3da6
Show file tree
Hide file tree
Showing 11 changed files with 728 additions and 53 deletions.
47 changes: 45 additions & 2 deletions mythtv/html/assets/i18n/en_US.json
Expand Up @@ -8,9 +8,16 @@
"savesuccess": "Saved Successfully",
"networkfail": "ERROR: Backend Network Failure",
"cancel": "Cancel",
"delete": "Delete",
"ok": "Ok",
"no": "No",
"yes": "Yes"
"yes": "Yes",
"default": "Default",
"close": "Close",
"continue": "Continue Editing",
"nosave": "Close without saving",
"unsaved_heading": "Unsaved Changes",
"unsaved_message": "There are unsaved changes."
},
"dashboard": {
"warning": "Please note this is a work in progress and some features are not fully working!"
Expand Down Expand Up @@ -645,7 +652,43 @@
"iconnections": {
"title": "Input Connections"
},
"clipbord_tooltip": "Copy to Clipboard"
"clipbord_tooltip": "Copy to Clipboard",
"chanedit": {
"title": "Channel Editor",
"always-visible": "Always Visible",
"visible": "Visible",
"not-visible": "Not Visible",
"never-visible": "Never Visible",
"new_channel": "New Channel",
"channame": "Channel Name",
"channame_desc": "Common name for the channel",
"channum": "Channel Number",
"channum_desc": "The number by which the channel is known to MythTV",
"freqid": "Frequency Id",
"freqid_desc": "Channel Number as understood by your tuners.",
"callsign": "Call Sign",
"callsign_desc": "Unique identifier for the transmitter.",
"visible_label": "Visibility",
"visible_desc": "If set to Always Visible or Visible, the channel will be visible in the EPG. Set to Always Visible or Never Visible to prevent MythTV and other utilities from automatically managing the value for this channel..",
"serviceid_label": "Service ID",
"serviceid_desc": "Service ID (Program Number) of desired channel within the transport stream. If there is only one channel, then setting this to anything will still find it.",
"source_label": "Video Source",
"source_desc": "It is NOT a good idea to change this value as it only changes the sourceid in table channel but not in dtv_multiplex. The sourceid in dtv_multiplex cannot and should not be changed.",
"format_label": "TV Format",
"format_desc": "If this channel uses a format other than TV Format in the General Backend Setup screen, set it here.",
"priority_label": "Priority",
"priority_desc": "\"Number of priority points to be added to any recording on this channel during scheduling. Use a positive number as the priority if you want this to be a preferred channel, a negative one to deprecate this channel.",
"eit_label": "Use On Air Guide",
"eit_desc": "If enabled, guide information for this channel will be updated using 'Over-the-Air' program listings.",
"xmltvid_label": "XMLTV ID",
"xmltvid_desc": "ID used by listing services to get an exact correspondence between a channel in your line-up and a channel in their database. Normally this is set automatically when 'mythfilldatabase' is run.",
"timeoffset_label": "Listings Time Offset",
"timeoffset_desc": "Offset (in minutes) to apply to the program guide data during import. This can be used when the listings for a particular channel are in a different time zone.",
"commmethod_label": "Commercial Detection Method",
"commmethod_desc": "Changes the method of commercial detection used for recordings on this channel or skips detection by marking the channel as Commercial Free.",
"delete_details": "This will delete Channel:{{ChanNum}}, Name:{{ChannelName}}, Source:{{Source}}",
"delete_this": "Delete Channel"
}
},
"sidenav": {
"settingsmenu": {
Expand Down
5 changes: 5 additions & 0 deletions mythtv/html/backend/src/app/app-routing.module.ts
Expand Up @@ -11,6 +11,7 @@ import { CaptureCardsComponent } from './config/settings/capture-cards/capture-c
import { VideoSourcesComponent } from './config/settings/video-sources/video-sources.component';
import { InputConnectionsComponent } from './config/settings/input-connections/input-connections.component';
import { StorageGroupsComponent } from './config/settings/storage-groups/storage-groups.component';
import { ChannelEditorComponent } from './config/settings/channel-editor/channel-editor.component';

const routes: Routes = [
{ path: '', component: DashboardComponent },
Expand All @@ -32,6 +33,10 @@ const routes: Routes = [
path: 'settings/input-connections', component: InputConnectionsComponent,
canDeactivate: [CanDeactivateGuardService]
},
{
path: 'settings/channel-editor', component: ChannelEditorComponent,
canDeactivate: [CanDeactivateGuardService]
},
{
path: 'settings/storage-groups', component: StorageGroupsComponent,
canDeactivate: [CanDeactivateGuardService]
Expand Down
2 changes: 2 additions & 0 deletions mythtv/html/backend/src/app/app.module.ts
Expand Up @@ -64,6 +64,7 @@ import { InputConnectionsComponent } from './config/settings/input-connections/i
import { IconnectionComponent } from './config/settings/input-connections/iconnection/iconnection.component';
import { StorageGroupsComponent } from './config/settings/storage-groups/storage-groups.component';
import { SgroupComponent } from './config/settings/storage-groups/sgroup/sgroup.component';
import { ChannelEditorComponent } from './config/settings/channel-editor/channel-editor.component';

// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient) {
Expand Down Expand Up @@ -124,6 +125,7 @@ export function HttpLoaderFactory(http: HttpClient) {
IconnectionComponent,
StorageGroupsComponent,
SgroupComponent,
ChannelEditorComponent,
],
imports: [
BrowserModule,
Expand Down
Empty file.
@@ -0,0 +1,243 @@
<form class="ml-3 mr-3" name="chanform" #chanform="ngForm">

<div class="block card w-full" style="height: calc(100vh - 200px)">
<h3>{{ 'settings.chanedit.title' | translate }}</h3>
<p-table [value]="allChannels" scrollHeight="flex" [scrollable]="true"
styleClass="p-datatable-gridlines p-datatable-striped" [rowHover]="true">
<ng-template pTemplate="caption">
<button pButton pRipple label="{{ 'settings.chanedit.new_channel' | translate }}" icon="pi pi-plus"
class="p-button-success mr-2" (click)="openNew()"></button>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th style="flex-grow: 4"></th>
<th style="flex-grow: 4">Chan Num</th>
<th style="flex-grow: 3">Freq Id</th>
<th style="flex-grow: 5">Call Sign</th>
<th style="flex-grow: 12">Name</th>
<th style="flex-grow: 8">Source</th>
<th style="flex-grow: 3">Priority</th>
<th style="flex-grow: 5">Visibility</th>
<th style="flex-grow: 3">Use EIT</th>
<th style="flex-grow: 3">
<!-- These are disabled buttons to ensure the spacing of the heading
matches the spacing of the rows -->
<button pButton pRipple icon="pi pi-pencil" class="p-button-text p-button-success"
disabled="true"></button>
<button pButton pRipple icon="pi pi-trash" class="p-button-text p-button-danger"
disabled="true"></button>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-channel>
<tr height="40" [ngClass]="{'line-through' : channel.ChanId < 0}">
<td style="flex-grow: 4">
<img src="{{channel.IconURL}}" height="32" width="42" *ngIf="channel.IconURL; else nullIcon"
style="background-color:#000000" onerror="this.height='0'">
<ng-template #nullIcon><img height="32" width="42"></ng-template>
</td>
<td style="flex-grow: 4">{{channel.ChanNum}}</td>
<td style="flex-grow: 3">{{channel.FrequencyId}}</td>
<td style="flex-grow: 5">{{channel.CallSign}}</td>
<td style="flex-grow: 12">{{channel.ChannelName}}</td>
<td style="flex-grow: 8">{{ getSource(channel) }}</td>
<td style="flex-grow: 3">{{channel.RecPriority}}</td>
<td style="flex-grow: 5">{{ getVisibility(channel) }}</td>
<td style="flex-grow: 3">{{ channel.UseEIT ? 'Y' : 'N' }}</td>
<td style="flex-grow: 3">
<button pButton pRipple icon="pi pi-pencil" class="p-button-text p-button-success"
(click)="editChannel(channel)" [disabled]="channel.ChanId < 0"></button>
<button pButton pRipple icon="pi pi-trash" class="p-button-text p-button-danger"
(click)="deleteRequest(channel)" [disabled]="channel.ChanId < 0"></button>
</td>
</tr>
</ng-template>
</p-table>

<p-dialog header="{{ dialogHeader }}" [(visible)]="displayChannelDlg" [modal]="true"
[style]="{height: '75vw', width: '50vw'}" [closable]="false" [closeOnEscape]="false">

<div class="form-group field">
<label for="ChannelName" class="block">{{ 'settings.chanedit.channame' | translate }}</label>
<input pInputText id="ChannelName" [(ngModel)]="channel.ChannelName" name="ChannelName"
#ChannelName="ngModel" class="mb-2 form-control w-full" />
<small class="block">
{{ 'settings.chanedit.channame_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="ChanNum" class="block">{{ 'settings.chanedit.channum' | translate }}</label>
<input pInputText id="ChanNum" [(ngModel)]="channel.ChanNum" name="ChanNum" #ChanNum="ngModel"
class="mb-2 form-control w-full" />
<small class="block">
{{ 'settings.chanedit.channum_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="FrequencyId" class="block">{{ 'settings.chanedit.freqid' | translate }}</label>
<input pInputText id="FrequencyId" [(ngModel)]="channel.FrequencyId" name="FrequencyId"
#FrequencyId="ngModel" class="mb-2 form-control w-full" />
<small class="block">
{{ 'settings.chanedit.freqid_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="CallSign" class="block">{{ 'settings.chanedit.callsign' | translate }}</label>
<input pInputText id="CallSign" [(ngModel)]="channel.CallSign" name="CallSign" #CallSign="ngModel"
class="mb-2 form-control w-full" />
<small class="block">
{{ 'settings.chanedit.callsign_desc' | translate }}
</small>
</div>

<div class="form-group field" *ngIf="transDone >= numTranslations">
<label for="ExtendedVisible" class="block">{{ 'settings.chanedit.visible_label' | translate
}}</label>
<p-dropdown [options]="visibilities" [(ngModel)]="channel.ExtendedVisible" [editable]="false"
optionLabel="prompt" optionValue="value" [maxlength]="0" [style]="{'minWidth':'400px'}"
name="ExtendedVisible" #ExtendedVisible="ngModel">
</p-dropdown>
<small class="block">
{{ 'settings.chanedit.visible_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="ServiceId" class="block">{{ 'settings.chanedit.serviceid_label' | translate
}}</label>
<p-inputNumber [(ngModel)]="channel.ServiceId" name="ServiceId" id="ServiceId" [showButtons]="true"
[min]="-1" [max]="65535" [step]="1"></p-inputNumber>
<small class="block">
{{ 'settings.chanedit.serviceid_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="SourceId" class="block">{{ 'settings.chanedit.source_label' | translate
}}</label>
<p-dropdown [options]="videoSources" [(ngModel)]="channel.SourceId" [editable]="false"
optionLabel="SourceName" optionValue="Id" [maxlength]="0" [style]="{'minWidth':'400px'}"
name="SourceId" #SourceId="ngModel">
</p-dropdown>
<small class="block">
{{ 'settings.chanedit.source_desc' | translate }}
</small>
</div>

<div class="form-group field" *ngIf="transDone >= numTranslations">
<label for="Format" class="block">{{ 'settings.chanedit.format_label' | translate
}}</label>
<p-dropdown [options]="tvFormats" [(ngModel)]="channel.Format" [editable]="false" optionLabel="prompt"
optionValue="value" [maxlength]="0" [style]="{'minWidth':'400px'}" name="Format" #Format="ngModel">
</p-dropdown>
<small class="block">
{{ 'settings.chanedit.format_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="RecPriority" class="block">{{ 'settings.chanedit.priority_label' | translate
}}</label>
<p-inputNumber [(ngModel)]="channel.RecPriority" name="RecPriority" id="RecPriority"
[showButtons]="true" [min]="-99" [max]="99" [step]="1"></p-inputNumber>
<small class="block">
{{ 'settings.chanedit.priority_desc' | translate }}
</small>
</div>

<div class="form-group field">
<p-checkbox inputId="UseEIT" [(ngModel)]="channel.UseEIT" name="UseEIT" #UseEIT="ngModel"
class="mb-2 w-full" [binary]="true" label="{{ 'settings.chanedit.eit_label' | translate }}">
</p-checkbox>
<small class="block">
{{ 'settings.chanedit.eit_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="XMLTVID" class="block">{{ 'settings.chanedit.xmltvid_label' | translate }}</label>
<input pInputText id="XMLTVID" [(ngModel)]="channel.XMLTVID" name="XMLTVID" #XMLTVID="ngModel"
class="mb-2 form-control w-full" />
<small class="block">
{{ 'settings.chanedit.xmltvid_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="TimeOffset" class="block">{{ 'settings.chanedit.timeoffset_label' | translate
}}</label>
<p-inputNumber [(ngModel)]="channel.TimeOffset" name="TimeOffset" id="TimeOffset" [showButtons]="true"
[min]="-1440" [max]="1440" [step]="30"></p-inputNumber>
<small class="block">
{{ 'settings.chanedit.timeoffset_desc' | translate }}
</small>
</div>

<div class="form-group field">
<label for="CommMethod" class="block">{{ 'settings.chanedit.commmethod_label' | translate
}}</label>
<p-dropdown [options]="commMethods" [(ngModel)]="channel.CommMethod" [editable]="false"
optionLabel="LocalizedName" optionValue="CommMethod" [maxlength]="0" [style]="{'minWidth':'400px'}"
name="CommMethod" #CommMethod="ngModel">
</p-dropdown>
<small class="block">
{{ 'settings.chanedit.commmethod_desc' | translate }}
</small>
</div>

<!-- Blank space here is so that the dropdown box can fit, otherwise there can be an impossibe
situation with the scroll bar and the drop box-->
<br><br><br><br><br><br><br><br>

<ng-template pTemplate="footer">
<div class="col-12" style="text-align: left;">
<p-message *ngIf="successCount > 0 && errorCount == 0 && !chanform.dirty" severity="success"
text="{{ 'common.savesuccess' | translate }}"></p-message>
<p-message *ngIf="errorCount > 0" severity="error"
text="{{ 'common.networkfail' | translate }}"></p-message>
</div>
<p-button icon="pi pi-times-circle" (onClick)="closeDialog()" label="{{ 'common.close' | translate }}"
styleClass="p-button-warning">
</p-button>
<p-button icon="pi pi-check-circle" (onClick)="saveChannel();" label="{{ 'common.save' | translate }}"
styleClass="p-button-success"
[disabled]="!chanform.dirty || channel.ChannelName.trim() == '' || channel.ChanNum.trim() == '' || channel.CallSign.trim() == '' "></p-button>
</ng-template>
</p-dialog>
<p-dialog header="{{ 'common.unsaved_heading' | translate }}" [(visible)]="displayUnsaved" [modal]="true">
<p>{{ 'common.unsaved_message' | translate }}</p>
<ng-template pTemplate="footer">
<p-button icon="pi pi-times-circle" (onClick)="closeDialog()" label="{{ 'common.nosave' | translate }}"
styleClass="p-button-danger">
</p-button>
<p-button icon="pi pi-check-circle" (onClick)="displayUnsaved=false"
label="{{ 'common.continue' | translate }}" styleClass="p-button-success">
</p-button>
</ng-template>
</p-dialog>

<p-dialog header="{{ 'settings.chanedit.delete_this' | translate }}" [(visible)]="displayDelete" [modal]="true"
[draggable]="false" [resizable]="false">
<h2>{{ 'settings.ru_sure' | translate }}</h2>
<p>{{ 'settings.chanedit.delete_details' |
translate: {ChanNum: channel.ChanNum,
ChannelName: channel.ChannelName,
Source: getSource(channel)} }}</p>
<ng-template pTemplate="footer">
<div class="col-12" style="text-align: left;">
<p-message *ngIf="errorCount > 0" severity="error"
text="{{ 'common.networkfail' | translate }}"></p-message>
</div>
<p-button icon="pi pi-times-circle" (click)="displayDelete=false"
label="{{ 'common.close' | translate }}" styleClass="p-button-warning">
</p-button>
<p-button icon="pi pi-check-circle" (click)="deleteChannel(channel)"
label="{{ 'common.delete' | translate }}" styleClass="p-button-danger"></p-button>
</ng-template>
</p-dialog>

</div>
</form>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ChannelEditorComponent } from './channel-editor.component';

describe('ChannelEditorComponent', () => {
let component: ChannelEditorComponent;
let fixture: ComponentFixture<ChannelEditorComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ChannelEditorComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(ChannelEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

0 comments on commit 91e3da6

Please sign in to comment.