344 changes: 187 additions & 157 deletions src/app/settings/settings.component.html

Large diffs are not rendered by default.

6 changes: 0 additions & 6 deletions src/app/settings/settings.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ button {
height: 70px !important;
}

.main-container {
display: grid;
grid-template-rows: 105px auto;
height: calc(100vh - 105px);
}

.settings-container {
overflow: auto;
height: calc(100vh - 140px);
Expand Down
10 changes: 5 additions & 5 deletions src/app/settings/settings.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UntypedFormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDividerModule } from '@angular/material/divider';
Expand Down Expand Up @@ -39,7 +39,7 @@ export class MockRouter {

const DEFAULT_SETTINGS = {
player: VideoPlayer.VideoJs,
epgUrl: '',
epgUrl: [],
language: Language.ENGLISH,
showCaptions: false,
theme: Theme.LightTheme,
Expand All @@ -62,7 +62,7 @@ describe('SettingsComponent', () => {
MockPipe(TranslatePipe),
],
providers: [
FormBuilder,
UntypedFormBuilder,
{ provide: MatSnackBar, useClass: MatSnackBarStub },
{
provide: TranslateService,
Expand Down Expand Up @@ -108,7 +108,7 @@ describe('SettingsComponent', () => {
});

describe('Get and set settings on component init', () => {
const settings = { player: 'test', showCaptions: true };
const settings = { player: 'test', showCaptions: true, epgUrl: [] };
let spyOnStorageGet;

beforeEach(() => {
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('SettingsComponent', () => {

it('should send epg fetch command', () => {
jest.spyOn(electronService, 'sendIpcEvent');
component.fetchEpg();
component.fetchEpg(['']);
expect(electronService.sendIpcEvent).toHaveBeenCalledWith(EPG_FETCH, {
url: '',
});
Expand Down
178 changes: 107 additions & 71 deletions src/app/settings/settings.component.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
UntypedFormArray,
UntypedFormBuilder,
UntypedFormControl,
Validators,
} from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { StorageMap } from '@ngx-pwa/local-storage';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import * as semver from 'semver';
import { EPG_FETCH } from '../../../shared/ipc-commands';
import { DataService } from '../services/data.service';
import { EpgService } from '../services/epg.service';
import { STORE_KEY } from '../shared/enums/store-keys.enum';
import { ChannelQuery } from '../state';
import { SettingsService } from './../services/settings.service';
import { Language } from './language.enum';
import { Settings, VideoPlayer } from './settings.interface';
import { Theme } from './theme.enum';

/** Url of the package.json file in the app repository, required to get the version of the released app */
const PACKAGE_JSON_URL =
'https://raw.githubusercontent.com/4gray/iptvnator/master/package.json';

@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
Expand All @@ -42,9 +40,6 @@ export class SettingsComponent implements OnInit {
},
];

/** Settings form object */
settingsForm: FormGroup;

/** Current version of the app */
version: string;

Expand All @@ -62,46 +57,46 @@ export class SettingsComponent implements OnInit {
/** All available visual themes */
themeEnum = Theme;

/** Settings form object */
settingsForm = this.formBuilder.group({
player: [VideoPlayer.VideoJs],
...(this.isElectron ? { epgUrl: new UntypedFormArray([]) } : {}),
language: Language.ENGLISH,
showCaptions: false,
theme: Theme.LightTheme,
});

/** Form array with epg sources */
epgUrl = this.settingsForm.get('epgUrl') as UntypedFormArray;

/**
* Creates an instance of SettingsComponent and injects
* required dependencies into the component
* @param channelQuery
* @param electronService
* @param formBuilder
* @param http
* @param router
* @param settingsService
* @param snackBar
* @param storage
* @param translate
*/
constructor(
private channelQuery: ChannelQuery,
private electronService: DataService,
private formBuilder: FormBuilder,
private http: HttpClient,
private epgService: EpgService,
private formBuilder: UntypedFormBuilder,
private router: Router,
private settingsService: SettingsService,
private snackBar: MatSnackBar,
private storage: StorageMap,
private translate: TranslateService
) {
this.settingsForm = this.formBuilder.group({
player: [VideoPlayer.VideoJs],
epgUrl: '',
language: Language.ENGLISH,
showCaptions: false,
theme: Theme.LightTheme,
});

this.checkAppVersion();
}
) {}

/**
* Reads the config object from the browsers
* storage (indexed db)
*/
ngOnInit(): void {
this.setSettings();
this.checkAppVersion();
}

/**
* Sets saved settings from the indexed db store
*/
setSettings(): void {
this.settingsService
.getValueFromLocalStorage(STORE_KEY.Settings)
.subscribe((settings: Settings) => {
Expand All @@ -110,7 +105,7 @@ export class SettingsComponent implements OnInit {
player: settings.player
? settings.player
: VideoPlayer.VideoJs,
epgUrl: settings.epgUrl ? settings.epgUrl : '',
...(this.isElectron ? { epgUrl: new Array() } : {}),
language: settings.language
? settings.language
: Language.ENGLISH,
Expand All @@ -121,27 +116,44 @@ export class SettingsComponent implements OnInit {
? settings.theme
: Theme.LightTheme,
});

if (this.isElectron) {
this.setEpgUrls(settings.epgUrl);
}
}
});
}

/**
* Sets the epg urls to the form array
* @param epgUrls urls of the EPG sources
*/
setEpgUrls(epgUrls: string[] | string): void {
const URL_REGEX = /^(http|https):\/\/[^ "]+$/;

if (!Array.isArray(epgUrls)) {
epgUrls = [epgUrls];
}

for (const url of epgUrls) {
this.epgUrl.push(
new UntypedFormControl(url, [
Validators.required,
Validators.pattern(URL_REGEX),
])
);
}
}

/**
* Checks whether the latest version of the application
* is used and updates the version message in the
* settings UI
*/
checkAppVersion(): void {
this.http
.get(PACKAGE_JSON_URL)
.pipe(
catchError((err) => {
console.error(err);
throw new Error(err);
})
)
.subscribe((response: { version: string }) => {
this.showVersionInformation(response.version);
});
this.settingsService.getAppVersion().subscribe((version) => {
this.showVersionInformation(version);
});
}

/**
Expand Down Expand Up @@ -181,26 +193,39 @@ export class SettingsComponent implements OnInit {
* the indexed db store
*/
onSubmit(): void {
this.storage
.set(STORE_KEY.Settings, this.settingsForm.value)
this.settingsService
.setValueToLocalStorage(
STORE_KEY.Settings,
this.settingsForm.value,
true
)
.subscribe(() => {
this.settingsForm.markAsPristine();
// check whether the epg url was changed or not
if (this.settingsForm.value.epgUrl) {
this.fetchEpg();
}
this.translate.use(this.settingsForm.value.language);
this.settingsService.changeTheme(this.settingsForm.value.theme);
this.snackBar.open(
this.translate.instant('SETTINGS.SETTINGS_SAVED'),
null,
{
duration: 2000,
}
);
this.applyChangedSettings();
});
}

/**
* Applies the changed settings to the app
*/
applyChangedSettings(): void {
this.settingsForm.markAsPristine();
// check whether the epg url was changed or not
if (this.isElectron) {
if (this.settingsForm.value.epgUrl) {
this.fetchEpg(this.settingsForm.value.epgUrl);
}
}
this.translate.use(this.settingsForm.value.language);
this.settingsService.changeTheme(this.settingsForm.value.theme);
this.snackBar.open(
this.translate.instant('SETTINGS.SETTINGS_SAVED'),
null,
{
duration: 2000,
}
);
}

/**
* Navigates back to the applications homepage
*/
Expand All @@ -210,14 +235,25 @@ export class SettingsComponent implements OnInit {

/**
* Fetches and updates EPG from the given URL
* @param urls epg source urls
*/
fetchEpg(): void {
this.electronService.sendIpcEvent(EPG_FETCH, {
url: this.settingsForm.value.epgUrl,
});
this.snackBar.open(this.translate.instant('EPG.FETCH_EPG'), 'Close', {
verticalPosition: 'bottom',
horizontalPosition: 'right',
});
fetchEpg(urls: string | string[]): void {
this.epgService.fetchEpg(urls);
}

/**
* Initializes new entry in form array for EPG URL
*/
addEpgSource(): void {
this.epgUrl.insert(this.epgUrl.length, new UntypedFormControl(''));
}

/**
* Removes entry from form array for EPG URL
* @param index index of the item to remove
*/
removeEpgSource(index: number): void {
this.epgUrl.removeAt(index);
this.settingsForm.markAsDirty();
}
}
2 changes: 1 addition & 1 deletion src/app/settings/settings.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export enum VideoPlayer {
*/
export interface Settings {
player: VideoPlayer;
epgUrl: string;
epgUrl: string[];
language: Language;
showCaptions: boolean;
theme: Theme;
Expand Down
50 changes: 29 additions & 21 deletions src/app/shared/components/header/header.component.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<div fxFlex="100px">
<div class="logo">
<img src="./assets/icons/icon-tv-256.png" height="100" />
</div>
<div fxFlex="100" fxLayoutAlign="start center">
<div fxLayout="column">
<div fxFlex class="title">{{ title }}</div>
<div class="subtitle">{{ subtitle }}</div>
</div>
<div class="main">
<div class="title">{{ title }}</div>
<div class="subtitle">{{ subtitle }}</div>
</div>
<div fxLayout="column" fxFlex="50px" fxLayoutAlign="center center">
<div class="menu">
<button
*ngIf="isElectron; else pwaMenu"
mat-icon-button
Expand All @@ -18,52 +16,62 @@
</div>

<ng-template #pwaMenu>
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Open menu">
<button
mat-icon-button
[matMenuTriggerFor]="menu"
[attr.aria-label]="'MENU.OPEN' | translate"
>
<mat-icon>menu</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="openSettings()">
<mat-icon>settings</mat-icon>
<span>Settings</span>
</button>
<mat-divider></mat-divider>
<ng-container *ngIf="isHome">
<button
mat-menu-item
[attr.aria-label]="'MENU.SETTINGS_ARIA' | translate"
(click)="openSettings()"
>
<mat-icon>settings</mat-icon>
<span>{{ 'MENU.SETTINGS' | translate }}</span>
</button>
<mat-divider></mat-divider>
</ng-container>
<button
mat-menu-item
aria-label="Support IPTVnator on GitHub"
[attr.aria-label]="'MENU.SUPPORT_ARIA' | translate"
(click)="openUrl('https:///github.com/4gray/iptvnator')"
>
<mat-icon>recommend</mat-icon>
<span>GitHub</span>
</button>
<button
mat-menu-item
aria-label="Report a bug"
[attr.aria-label]="'MENU.BUG_REPORT' | translate"
(click)="openUrl('https://github.com/4gray/iptvnator/issues')"
>
<mat-icon>bug_report</mat-icon>
<span>Report a bug</span>
<span>{{ 'MENU.BUG_REPORT' | translate }}</span>
</button>
<mat-divider></mat-divider>
<button
mat-menu-item
aria-label="What is new?"
[attr.aria-label]="'MENU.WHAT_IS_NEW' | translate"
(click)="setDialogVisibility(true)"
>
<mat-icon>new_releases</mat-icon>
<span>What is new</span>
<span>{{ 'MENU.WHAT_IS_NEW' | translate }}</span>
</button>
<button
mat-menu-item
aria-label="About this application"
[attr.aria-label]="'MENU.ABOUT_ARIA' | translate"
(click)="openAboutDialog()"
>
<mat-icon>info</mat-icon>
<span>About</span>
<span>{{ 'MENU.ABOUT' | translate }}</span>
</button>
</mat-menu>
</ng-template>
<ngx-whats-new
*ngIf="isDialogVisible$ | async"
*ngIf="!isElectron && isDialogVisible$ | async"
(closeModal)="setDialogVisibility(false)"
[items]="modals"
[options]="options"
Expand Down
36 changes: 30 additions & 6 deletions src/app/shared/components/header/header.component.scss
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
:host {
background: #000;
color: #fff;
display: flex;
width: 100%;
}

.title {
font-size: 1.1em;
.logo {
flex: 1 1 100px;
}

.subtitle {
color: #999;
font-size: 0.9em;
margin-top: 10px;
.main {
place-content: center flex-start;
align-self: center;
flex-direction: column;
box-sizing: border-box;
display: flex;
flex: 1 1 100%;
max-width: 100%;

.title {
font-size: 1.1em;
}

.subtitle {
color: #999;
font-size: 0.9em;
margin-top: 10px;
}
}

.menu {
place-content: center;
align-self: center;
min-width: 50px;
max-width: 50px;
display: flex;
}
31 changes: 18 additions & 13 deletions src/app/shared/components/header/header.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { HomeComponent } from '../../../home/home.component';
import { DataService } from '../../../services/data.service';
import { WhatsNewService } from '../../../services/whats-new.service';
import { AboutDialogComponent } from '../about-dialog/about-dialog.component';
Expand All @@ -10,12 +11,12 @@ import { AboutDialogComponent } from '../about-dialog/about-dialog.component';
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent {
export class HeaderComponent implements OnInit {
/** Title of the header */
@Input() title: string;
@Input() title!: string;

/** Subtitle of the header */
@Input() subtitle: string;
@Input() subtitle!: string;

/** Environment flag */
isElectron = this.dataService.isElectron;
Expand All @@ -29,18 +30,22 @@ export class HeaderComponent {
/** Modals to show for the updated version of the application */
modals = this.whatsNewService.getLatestChanges();

/** Creates an instance of SettingsComponent and injects angulars router module
* @param router angulars router
* @param dataService
* @param whatsNewService
*/
isHome = true;

/** Creates an instance of SettingsComponent */
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private dialog: MatDialog,
private dataService: DataService,
private whatsNewService: WhatsNewService,
private dialog: MatDialog
private router: Router,
private whatsNewService: WhatsNewService
) {}

ngOnInit() {
this.isHome =
this.activatedRoute.snapshot.component.name === HomeComponent.name;
}

/**
* Navigates to the settings page
*/
Expand Down
16 changes: 16 additions & 0 deletions src/app/shared/playlist-meta.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Playlist } from '../../../shared/playlist.interface';

export type PlaylistMeta = Pick<
Playlist,
| 'count'
| 'title'
| 'filename'
| '_id'
| 'url'
| 'importDate'
| 'userAgent'
| 'filePath'
| 'updateDate'
| 'updateState'
| 'position'
>;
4 changes: 2 additions & 2 deletions src/app/state/channel.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ export class ChannelStore extends EntityStore<ChannelState> {
*/
setPlaylist(playlist: Playlist): void {
this.remove();
const favorites = playlist.favorites || [];
const channels = playlist.playlist.items.map((element) =>
const favorites = playlist?.favorites || [];
const channels = playlist?.playlist.items.map((element) =>
createChannel(element)
);
this.upsertMany(channels);
Expand Down
27 changes: 23 additions & 4 deletions src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,24 @@
"REMOVE_DIALOG": {
"MESSAGE": "Sind Sie sicher, dass Sie diese Wiedergabeliste vollständig löschen möchten",
"TITLE": ""
}
},
"GLOBAL_FAVORITES": "Globale Favoriten",
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
"MY_PLAYLISTS": "Meine Playlisten"
},
"TABS": {
"FILE_UPLOAD": "Aus Dateisystem hochladen",
"RECENTLY_ADDED": "Zuletzt hinzugefügt",
"URL_UPLOAD": "Via URL hinzufügen"
"URL_UPLOAD": "Via URL hinzufügen",
"TEXT_IMPORT": "Als Text importieren"
},
"URL_UPLOAD": {
"ADD_PLAYLIST": "Playlist hinzufügen",
"PLAYLIST_URL": "URL der Playlist"
},
"TEXT_IMPORT": {
"LABEL": "Füge m3u(8)-Wiedergabeliste hier als Text ein",
"BUTTON_LABEL": "Importieren"
}
},
"LANGUAGES": {
Expand Down Expand Up @@ -119,8 +127,19 @@
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "Senderliste öffnen",
"OPEN_EPG_LIST": "TV-Guide (EPG) öffnen",
"TOGGLE_FAVORITE_FLAG": "Als Favoriten hinzufügen/entfernen"
"TOGGLE_FAVORITE_FLAG": "Als Favoriten hinzufügen/entfernen",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"NO": "Nein",
"YES": "Ja"
"YES": "Ja",
"MENU": {
"OPEN": "Menü öffnen",
"SUPPORT_ARIA": "IPTVnator auf GitHub unterstützen",
"SETTINGS": "Einstellungen",
"SETTINGS_ARIA": "Einstellungen öffnen",
"BUG_REPORT": "Fehler melden",
"WHAT_IS_NEW": "Was ist neu",
"ABOUT": "Über",
"ABOUT_ARIA": "Über die Anwendung"
}
}
27 changes: 23 additions & 4 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"TABS": {
"RECENTLY_ADDED": "Recently added playlists",
"FILE_UPLOAD": "Add via file upload",
"URL_UPLOAD": "Add via URL"
"URL_UPLOAD": "Add via URL",
"TEXT_IMPORT": "Import as text"
},
"PLAYLISTS": {
"NO_PLAYLISTS": "No playlists were added",
Expand Down Expand Up @@ -38,7 +39,10 @@
"MESSAGE": "Are you sure you want to delete this playlist completely?"
},
"UPDATED": "Updated",
"REFRESH": "Refresh playlist"
"REFRESH": "Refresh playlist",
"GLOBAL_FAVORITES": "Global favorites",
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
"MY_PLAYLISTS": "My playlists"
},
"FILE_UPLOAD": {
"DRAG_DROP": "Drag & drop files here",
Expand All @@ -48,6 +52,10 @@
"URL_UPLOAD": {
"PLAYLIST_URL": "Playlist URL (m3u, m3u8)",
"ADD_PLAYLIST": "Add playlist"
},
"TEXT_IMPORT": {
"LABEL": "Insert m3u(8) playlist as text",
"BUTTON_LABEL": "Import"
}
},
"SETTINGS": {
Expand Down Expand Up @@ -88,7 +96,8 @@
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "Open channel list",
"TOGGLE_FAVORITE_FLAG": "Toggle favorite flag",
"OPEN_EPG_LIST": "Open EPG list"
"OPEN_EPG_LIST": "Open EPG list",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"EPG": {
"NEXT_DAY": "Next day",
Expand Down Expand Up @@ -122,5 +131,15 @@
"FRENCH": "Français"
},
"YES": "Yes",
"NO": "No"
"NO": "No",
"MENU": {
"OPEN": "Open menu",
"SUPPORT_ARIA": "Support IPTVnator on GitHub",
"SETTINGS": "Settings",
"SETTINGS_ARIA": "Open settings",
"BUG_REPORT": "Report a bug",
"WHAT_IS_NEW": "What is new",
"ABOUT": "About",
"ABOUT_ARIA": "About the application"
}
}
27 changes: 23 additions & 4 deletions src/assets/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"TABS": {
"RECENTLY_ADDED": "Listas añadidas recientemente",
"FILE_UPLOAD": "Añadir y cargar archivo",
"URL_UPLOAD": "Añadir por url"
"URL_UPLOAD": "Añadir por url",
"TEXT_IMPORT": "Import as text"
},
"PLAYLISTS": {
"NO_PLAYLISTS": "No se añadieron listas de reproducción",
Expand Down Expand Up @@ -38,7 +39,10 @@
"MESSAGE": "¿Estás seguro de quitar esta lista de reproducción por completo?"
},
"UPDATED": "Actualizado",
"REFRESH": "Actualizar lista"
"REFRESH": "Actualizar lista",
"GLOBAL_FAVORITES": "Global favorites",
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
"MY_PLAYLISTS": "My playlists"
},
"FILE_UPLOAD": {
"DRAG_DROP": "Arrastra & suelta archivos aquí",
Expand All @@ -48,6 +52,10 @@
"URL_UPLOAD": {
"PLAYLIST_URL": "Lista de reproducción (m3u, m3u8)",
"ADD_PLAYLIST": "Añadir lista"
},
"TEXT_IMPORT": {
"LABEL": "Insert m3u(8) playlist as text",
"BUTTON_LABEL": "Import"
}
},
"SETTINGS": {
Expand Down Expand Up @@ -88,7 +96,8 @@
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "Open channels list",
"TOGGLE_FAVORITE_FLAG": "Toggle favorite flag",
"OPEN_EPG_LIST": "Open EPG list"
"OPEN_EPG_LIST": "Open EPG list",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"EPG": {
"NEXT_DAY": "Día siguiente",
Expand Down Expand Up @@ -122,5 +131,15 @@
"FRENCH": "Français"
},
"YES": "",
"NO": "No"
"NO": "No",
"MENU": {
"OPEN": "Open menu",
"SUPPORT_ARIA": "Support IPTVnator on GitHub",
"SETTINGS": "Settings",
"SETTINGS_ARIA": "Open settings",
"BUG_REPORT": "Report a bug",
"WHAT_IS_NEW": "What is new",
"ABOUT": "About",
"ABOUT_ARIA": "About the application"
}
}
93 changes: 56 additions & 37 deletions src/assets/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
"CLOSE": "Fermer",
"HOME": {
"TITLE": "IPTVnator",
"SUBTITLE": "Veuillez choisir une liste de lecture dans la list, ou ajouter une nouvelle liste de lecture par transfert de fichier ou par URL",
"SUBTITLE": "Choisissez une liste de lecture, ou ajoutez en une nouvelle via un transfert de fichier ou via une URL",
"TABS": {
"RECENTLY_ADDED": "Listes de lecture ajoutés récemment",
"FILE_UPLOAD": "Ajouter par transfert de fichier",
"URL_UPLOAD": "Ajouter par URL"
"RECENTLY_ADDED": "Listes de lecture ajoutées récemment",
"FILE_UPLOAD": "Ajouter un fichier",
"URL_UPLOAD": "Ajouter une URL",
"TEXT_IMPORT": "Import as text"
},
"PLAYLISTS": {
"NO_PLAYLISTS": "Aucune liste de lecteur n'a été ajouté",
"ADD_FIRST": "Veuillez ajouter votre première liste de lecteur par un autre onglet",
"ADDED_VIA_URL": "Ajouté par URL. URL:",
"NO_PLAYLISTS": "Aucune liste de lecture n'a été ajouté",
"ADD_FIRST": "Veuillez ajouter votre première liste de lecture depuis un autre onglet",
"ADDED_VIA_URL": "Ajouté par URL. URL :",
"ADDED_VIA_FILE": "Ajouté par transfert de fichier",
"CHANNELS": "Canaux",
"CHANNELS": "Chaines",
"ADDED": "Ajouté",
"SHOW_DETAILS": "Afficher les détails de la liste de lecture",
"REMOVE": "Retirer la liste de lecture",
Expand All @@ -22,94 +23,102 @@
"TITLE": "Titre",
"FROM_URL": "Ajouté par URL",
"IMPORT_DATE": "Date d'importation",
"CHANNELS": "Nombre de canaux",
"CHANNELS": "Nombre de chaines",
"CLOSE": "Fermer",
"SAVE": "Enregistrer",
"USER_AGENT": "Agent utilisateur",
"ORIGINAL_FILENAME": "Nom de fichier d'origine",
"ORIGINAL_FILENAME": "Nom du fichier d'origine",
"FILE_PATH": "Chemin du fichier",
"UPDATE_FAILED": "Échec de la dernière mise à jour de liste de lecture",
"AUTO_UPDATE": "Mise à jour automatique",
"AUTO_UPDATE_DESCRIPTION": "Si la fonction de mise à jour automatique est activé, la liste de lecteur sera mis à jour automatiquement à chaque fois que l'app est lancé.",
"AUTO_UPDATE_DESCRIPTION": "Si la fonction de mise à jour automatique est activée, la liste de lecture sera mise à jour automatiquement à chaque fois que l'app est lancée.",
"CUSTOM_USER_AGENT": "Certains fournisseurs d'IPTC requiert un agent utilisateur spécifique afin de lire leur liste de lecture."
},
"REMOVE_DIALOG": {
"TITLE": "Retirer la liste de lecture",
"MESSAGE": "Êtes-vous certain de vouloir complètement retirer cette liste de lecture?"
"MESSAGE": "Êtes-vous certain de vouloir complètement retirer cette liste de lecture ?"
},
"UPDATED": "Mis à jour",
"REFRESH": "Mettre à jour la liste de lecture"
"REFRESH": "Mettre à jour la liste de lecture",
"GLOBAL_FAVORITES": "Global favorites",
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
"MY_PLAYLISTS": "My playlists"
},
"FILE_UPLOAD": {
"DRAG_DROP": "Glissez un fichier ici",
"OR": "ou",
"CHOOSE_PLAYLIST": "Choisir une liste de lecture"
},
"URL_UPLOAD": {
"PLAYLIST_URL": " URL de la liste de lecture(m3u, m3u8)",
"ADD_PLAYLIST": "Ajouter une liste de lecture"
"PLAYLIST_URL": "URL de la liste de lecture (m3u, m3u8)",
"ADD_PLAYLIST": "Ajouter la liste de lecture"
},
"TEXT_IMPORT": {
"LABEL": "Insert m3u(8) playlist as text",
"BUTTON_LABEL": "Import"
}
},
"SETTINGS": {
"TITLE": "Réglages / IPTVnator",
"SUBTITLE": "Changer la configuration de l'application",
"DESCRIPTION": "Veuillez ajuster les options dans la liste ci-dessous et confirmer avec le bouton Enregistrer.",
"TITLE": "Paramètres / IPTVnator",
"SUBTITLE": "Modifiez la configuration de l'application",
"DESCRIPTION": "Changez les options de la liste ci-dessous et enregistrez vos modifications.",
"EPG_URL_LABEL": "URL du EPG (*.xml ou *.xml.gz)",
"EPG_URL_PLACEHOLDER": "Saisir une URL",
"REFRESH_EPG": "Mise à jour du EPG",
"REFRESH_EPG": "Mise à jour de l'EPG",
"VIDEO_PLAYER_LABEL": "Lecteur vidéo",
"VIDEO_PLAYER_PLACEHOLDER": "Choisir une option",
"VERSION": "Version",
"SAVE_CHANGES": "Enregistrer les modifications",
"BACK_TO_HOME": "Retour à l'accueil",
"NEW_VERSION_AVAILABLE": "Il y a une nouvelle version disponible",
"BACK_TO_HOME": "Retourner à l'accueil",
"NEW_VERSION_AVAILABLE": "Une nouvelle version est disponible",
"LATEST_VERSION": "Vous utilisez la dernière version",
"LANGUAGE": "Langue",
"SETTINGS_SAVED": "La configuration est enregistré avec succès.",
"THEME": "Thème visuel",
"SETTINGS_SAVED": "La configuration a été enregistrée avec succès.",
"THEME": "Thème",
"SHOW_CAPTIONS": "Afficher les sous-titres"
},
"THEMES": {
"DARK_THEME": "Thème foncé",
"LIGHT_THEME": "Thème clair"
},
"CHANNELS": {
"SEARCH_CHANNEL": "Rechercher un canal",
"SEARCH_CHANNEL": "Rechercher une chaine",
"UPLOAD_OR_SELECT_OTHER_PLAYLIST": "Transférer ou choisir une autre liste de lecture",
"ALL_CHANNELS": "Tous les canaux",
"ALL_CHANNELS": "Chaines",
"GROUPS": "Groupes",
"UNGROUPED": "Non-groupé",
"UNNAMED_CHANNEL": "Canal sans non",
"UNNAMED_CHANNEL": "Chaine sans non",
"FAVORITES": "Favoris",
"NO_FAVORITES": "Aucun favoris",
"USE_STAR_TO_FAVORITE": "Veuillez utiliser l'icône d'étoile pour ajouter un canal à cette liste.",
"USE_STAR_TO_FAVORITE": "Veuillez utiliser l'icône 'étoile' pour ajouter une chaine aux favoris.",
"REMOVE_FAVORITE": "Retirer des favoris"
},
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "Ouvrir la liste des canaux",
"OPEN_CHANNELS_LIST": "Ouvrir la liste des chaines",
"TOGGLE_FAVORITE_FLAG": "Basculer le status de favoris",
"OPEN_EPG_LIST": "Ouvrir la liste du EPG"
"OPEN_EPG_LIST": "Ouvrir la liste du EPG",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"EPG": {
"NEXT_DAY": "Jour suivant",
"PREVIOUS_DAY": "Jour précédent",
"LIVE_NOW": "En direct",
"LIVE_STREAM": "Diffusion en direct",
"TIMESHIFT_AVAILABLE": "Contrôle du direct, appuyez pour lire",
"EPG_NOT_AVAILABLE_DATE": "Échec: Le EPG n'est pas disponible pour la date choisie",
"EPG_NOT_AVAILABLE_CHANNEL_TITLE": "Échec: Le EPG n'est pas disponible pour ce canal.",
"EPG_NOT_AVAILABLE_DATE": "Échec: L'EPG n'est pas disponible pour la date choisie",
"EPG_NOT_AVAILABLE_CHANNEL_TITLE": "Échec: L'EPG n'est pas disponible pour cette chaine",
"EPG_NOT_AVAILABLE_CHANNEL_DESCRIPTION": "Veuillez ajouter/modifier l'URL de la liste EPG dans les réglages de l'app.",
"FETCH_EPG": "Récupération des données EPG...",
"ERROR": "chec: Le EPG n'a pas pu être récupéré.",
"DOWNLOAD_SUCCESS": "EPG récupéré avec succès.",
"ERROR": "Échec: L'EPG n'a pas pu être récupéré",
"DOWNLOAD_SUCCESS": "L'EPG a été récupéré avec succès",
"PROGRAM_DIALOG": {
"PROGRAM_DETAILS": "Détails à propos du programme télé",
"PROGRAM_DETAILS": "Détails du programme TV",
"TITLE": "Titre",
"CATEGORY": "Catégorie",
"PARENTAL_RATING_SYSTEM": "Système de contrôle parental",
"PARENTAL_RATING_SYSTEM": "Paramètres du contrôle parental",
"DESCRIPTION": "Description",
"LANGUAGE": "Langue",
"SHOW_PROGRAM_DETAILS": "Afficher les détails à propos du programme télé"
"SHOW_PROGRAM_DETAILS": "Afficher les détails du programme TV"
}
},
"LANGUAGES": {
Expand All @@ -122,5 +131,15 @@
"CHINESE": "简体中文"
},
"YES": "Oui",
"NO": "Non"
"NO": "Non",
"MENU": {
"OPEN": "Open menu",
"SUPPORT_ARIA": "Support IPTVnator on GitHub",
"SETTINGS": "Settings",
"SETTINGS_ARIA": "Open settings",
"BUG_REPORT": "Report a bug",
"WHAT_IS_NEW": "What is new",
"ABOUT": "About",
"ABOUT_ARIA": "About the application"
}
}
27 changes: 23 additions & 4 deletions src/assets/i18n/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"TABS": {
"RECENTLY_ADDED": "최근 재생 목록이 추가되었습니다",
"FILE_UPLOAD": "파일 업로드를 통해 추가하십시오",
"URL_UPLOAD": "URL을 통해 추가하십시오"
"URL_UPLOAD": "URL을 통해 추가하십시오",
"TEXT_IMPORT": "Import as text"
},
"PLAYLISTS": {
"NO_PLAYLISTS": "재생 목록이 추가되지 않았습니다",
Expand Down Expand Up @@ -38,7 +39,10 @@
"MESSAGE": "이 재생 목록을 완전히 삭제 하시겠습니까?"
},
"UPDATED": "업데이트 됨",
"REFRESH": "재생 목록 새로 고침"
"REFRESH": "재생 목록 새로 고침",
"GLOBAL_FAVORITES": "Global favorites",
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
"MY_PLAYLISTS": "My playlists"
},
"FILE_UPLOAD": {
"DRAG_DROP": "파일을 드래그 앤 드롭",
Expand All @@ -48,6 +52,10 @@
"URL_UPLOAD": {
"PLAYLIST_URL": "재생 목록 URL (m3u, m3u8)",
"ADD_PLAYLIST": "재생 목록을 추가하십시오"
},
"TEXT_IMPORT": {
"LABEL": "Insert m3u(8) playlist as text",
"BUTTON_LABEL": "Import"
}
},
"SETTINGS": {
Expand Down Expand Up @@ -88,7 +96,8 @@
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "채널 열기 목록",
"TOGGLE_FAVORITE_FLAG": "즐겨찾기 보이기/숨기기",
"OPEN_EPG_LIST": "EPG 목록 열기"
"OPEN_EPG_LIST": "EPG 목록 열기",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"EPG": {
"NEXT_DAY": "다음날",
Expand Down Expand Up @@ -122,5 +131,15 @@
"FRENCH": "Français"
},
"YES": "Yes",
"NO": "No"
"NO": "No",
"MENU": {
"OPEN": "Open menu",
"SUPPORT_ARIA": "Support IPTVnator on GitHub",
"SETTINGS": "Settings",
"SETTINGS_ARIA": "Open settings",
"BUG_REPORT": "Report a bug",
"WHAT_IS_NEW": "What is new",
"ABOUT": "About",
"ABOUT_ARIA": "About the application"
}
}
27 changes: 23 additions & 4 deletions src/assets/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"TABS": {
"RECENTLY_ADDED": "Недавно добавленные плейлисты",
"FILE_UPLOAD": "Загрузить с компьютера",
"URL_UPLOAD": "Загрузить по ссылке"
"URL_UPLOAD": "Загрузить по ссылке",
"TEXT_IMPORT": "Импортировать из текста"
},
"PLAYLISTS": {
"NO_PLAYLISTS": "Пока еще не было добавленно ни одного плейлиста",
Expand Down Expand Up @@ -38,7 +39,10 @@
"REMOVE_DIALOG": {
"MESSAGE": "Вы уверены, что хотите удалить этот плейлист?",
"TITLE": ""
}
},
"GLOBAL_FAVORITES": "Все фавориты",
"GLOBAL_FAVORITES_DESCRIPTION": "Автоматически сгенерированный плейлист, включающий в себя фавориты из всех плейлистов",
"MY_PLAYLISTS": "Мои плейлисты"
},
"FILE_UPLOAD": {
"DRAG_DROP": "Перетащите файл в эту область",
Expand All @@ -48,6 +52,10 @@
"URL_UPLOAD": {
"PLAYLIST_URL": "Ссылка на плейлист (*.m3u или *.m3u8)",
"ADD_PLAYLIST": "Добавить плейлист"
},
"TEXT_IMPORT": {
"LABEL": "Вставьте плейлист формата m3u(8) в данное поле",
"BUTTON_LABEL": "Импортировать"
}
},
"SETTINGS": {
Expand Down Expand Up @@ -88,7 +96,8 @@
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "Открыть список каналов",
"TOGGLE_FAVORITE_FLAG": "Избранный",
"OPEN_EPG_LIST": "Открыть программу (EPG)"
"OPEN_EPG_LIST": "Открыть программу (EPG)",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"EPG": {
"NEXT_DAY": "Следующий день",
Expand Down Expand Up @@ -122,5 +131,15 @@
"FRENCH": "Français"
},
"NO": "Нет",
"YES": "Да"
"YES": "Да",
"MENU": {
"OPEN": "Открыть меню",
"SUPPORT_ARIA": "Поддержать IPTVnator на GitHub",
"SETTINGS": "Настройки",
"SETTINGS_ARIA": "Открыть настройки",
"BUG_REPORT": "Сообщить об ошибке",
"WHAT_IS_NEW": "Что нового?",
"ABOUT": "О приложении",
"ABOUT_ARIA": "О приложении"
}
}
27 changes: 23 additions & 4 deletions src/assets/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"TABS": {
"RECENTLY_ADDED": "最近添加的播放列表",
"FILE_UPLOAD": "从文件添加",
"URL_UPLOAD": "从URL链接添加"
"URL_UPLOAD": "从URL链接添加",
"TEXT_IMPORT": "Import as text"
},
"PLAYLISTS": {
"NO_PLAYLISTS": "未添加播放列表",
Expand Down Expand Up @@ -38,7 +39,10 @@
"MESSAGE": "您确定要完全删除此播放列表吗?"
},
"UPDATED": "更新",
"REFRESH": "刷新播放列表"
"REFRESH": "刷新播放列表",
"GLOBAL_FAVORITES": "Global favorites",
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
"MY_PLAYLISTS": "My playlists"
},
"FILE_UPLOAD": {
"DRAG_DROP": "将文件拖放到此处",
Expand All @@ -48,6 +52,10 @@
"URL_UPLOAD": {
"PLAYLIST_URL": "播放一个播放列表(m3u, m3u8)",
"ADD_PLAYLIST": "添加播放列表"
},
"TEXT_IMPORT": {
"LABEL": "Insert m3u(8) playlist as text",
"BUTTON_LABEL": "Import"
}
},
"SETTINGS": {
Expand Down Expand Up @@ -88,7 +96,8 @@
"TOP_MENU": {
"OPEN_CHANNELS_LIST": "打开频道列表",
"TOGGLE_FAVORITE_FLAG": "切换收藏夹标志",
"OPEN_EPG_LIST": "打开电子节目单EPG列表"
"OPEN_EPG_LIST": "打开电子节目单EPG列表",
"OPEN_MULTI_EPG": "Open Multi-EPG view"
},
"EPG": {
"NEXT_DAY": "明天",
Expand Down Expand Up @@ -122,5 +131,15 @@
"FRENCH": "Français"
},
"YES": "",
"NO": ""
"NO": "",
"MENU": {
"OPEN": "Open menu",
"SUPPORT_ARIA": "Support IPTVnator on GitHub",
"SETTINGS": "Settings",
"SETTINGS_ARIA": "Open settings",
"BUG_REPORT": "Report a bug",
"WHAT_IS_NEW": "What is new",
"ABOUT": "About",
"ABOUT_ARIA": "About the application"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/updates/0110/multi-epg-view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/updates/0110/multiple-epg-sources.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 10 additions & 3 deletions src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ $material-icons-font-path: '~material-icons/iconfont/';

body,
html {
background: #fff;
user-select: none;
margin: 0;
padding: 0;
Expand All @@ -22,6 +21,7 @@ html {
@media (display-mode: standalone) {
body {
overscroll-behavior-y: contain;
overflow: hidden;
}
}

Expand Down Expand Up @@ -81,8 +81,6 @@ textarea,
}

.cet-container {
height: calc(100vh);

.video-js {
height: calc(100vh - 82px) !important;
}
Expand All @@ -95,4 +93,13 @@ textarea,
.recent-playlists {
height: calc(100vh - 152px - 28px) !important;
}

#groups-list,
#favorites-list {
height: calc(100vh - 148px) !important;
}

.scroll-viewport {
min-height: calc(100vh - 200px) !important;
}
}
2 changes: 1 addition & 1 deletion src/themes/dark.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$dark-primary: mat-palette($black, A200, A800, A500);
$dark-primary: mat-palette($black, A200, A700, 500);
$dark-accent: mat-palette($mat-teal, A400, A100, A400);
$dark-warn: mat-palette($mat-red);
$dark-theme: mat-dark-theme(
Expand Down
4 changes: 2 additions & 2 deletions src/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"outDir": "../out-tsc/app",
"module": "es2020",
"baseUrl": "",
"types": []
"types": ["node"]
},
"include": ["**/*.ts"],
"exclude": ["**/*.spec.ts", "**/*.stub.ts"],
"exclude": ["**/*.spec.ts", "**/*.stub.ts", "**/*.worker.ts"],
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
Expand Down
5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"target": "es2020",
"typeRoots": ["node_modules/@types"],
"lib": ["es2017", "es2016", "es2015", "dom"],
"resolveJsonModule": true,
"strict": false
"strict": false,
"skipLibCheck": true
},
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.d.ts"],
Expand Down
45 changes: 17 additions & 28 deletions tsconfig.serve.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"types": [
"node"
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"types": ["node"],
"lib": ["es2017", "es2016", "es2015", "dom"]
},
"files": [
"electron/api.ts",
"electron/epg-worker.ts",
"electron/main.ts",
"electron/menu.ts",
"electron/preload.ts"
],
"lib": [
"es2017",
"es2016",
"es2015",
"dom"
]
},
"files": [
"api.ts",
"epg-worker.ts",
"main.ts",
"menu.ts",
"preload.ts"
],
"exclude": [
"node_modules",
"**/*.spec.ts",
"**/*.stub.ts"
]
"exclude": ["node_modules", "**/*.spec.ts", "**/*.stub.ts"]
}
2 changes: 1 addition & 1 deletion tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"types": ["jest", "node"],
"types": ["jest", "node", "electron"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"resolveJsonModule": true
Expand Down
10 changes: 10 additions & 0 deletions tsconfig.worker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/worker",
"lib": ["es2018", "webworker", "dom"],
"types": []
},
"include": ["src/**/*.worker.ts"]
}