mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat: add "what is new" dialog
ci: adapt registry config in ci scripts ci: adapt registry config in ci scripts ci: adapt registry config in ci scripts refactor: revert ci changes, update import of dependency
This commit is contained in:
13 files changed
+42272
-94
No files matched your search
@@ -7,3 +7,6 @@ export const EPG_GET_PROGRAM_DONE = 'EPG:GET_PROGRAM_DONE';
|
|||||||
|
|
||||||
// Playlist related commands
|
// Playlist related commands
|
||||||
export const PLAYLIST_SAVE_DETAILS = 'PLAYLIST:SAVE_DETAILS';
|
export const PLAYLIST_SAVE_DETAILS = 'PLAYLIST:SAVE_DETAILS';
|
||||||
|
|
||||||
|
// General
|
||||||
|
export const SHOW_WHATS_NEW = 'SHOW_WHATS_NEW';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron';
|
import { app, BrowserWindow, Menu, MenuItem, shell } from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { SHOW_WHATS_NEW } from './ipc-commands';
|
||||||
const openAboutWindow = require('about-window').default;
|
const openAboutWindow = require('about-window').default;
|
||||||
|
|
||||||
export class AppMenu {
|
export class AppMenu {
|
||||||
@@ -75,6 +76,10 @@ export class AppMenu {
|
|||||||
return new MenuItem({
|
return new MenuItem({
|
||||||
label: 'Help',
|
label: 'Help',
|
||||||
submenu: [
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'What is new',
|
||||||
|
click: () => this.window.webContents.send(SHOW_WHATS_NEW),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Report a bug',
|
label: 'Report a bug',
|
||||||
click: () =>
|
click: () =>
|
||||||
|
|||||||
Generated
+42042
-55
File diff suppressed because it is too large.
Load diff
@@ -128,6 +128,7 @@
|
|||||||
"nedb-promises-ts": "0.0.1-4",
|
"nedb-promises-ts": "0.0.1-4",
|
||||||
"ngx-filter-pipe": "2.1.2",
|
"ngx-filter-pipe": "2.1.2",
|
||||||
"ngx-uploader": "11.0.0",
|
"ngx-uploader": "11.0.0",
|
||||||
|
"ngx-whats-new": "0.0.1",
|
||||||
"semver": "7.3.4",
|
"semver": "7.3.4",
|
||||||
"uuid": "8.3.0",
|
"uuid": "8.3.0",
|
||||||
"video-js": "0.7.0"
|
"video-js": "0.7.0"
|
||||||
|
|||||||
@@ -1 +1,9 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
<!-- whats new dialog -->
|
||||||
|
<ngx-whats-new
|
||||||
|
*ngIf="isDialogVisible$ | async"
|
||||||
|
(closeModal)="setDialogVisibility(false)"
|
||||||
|
[items]="modals"
|
||||||
|
[options]="options"
|
||||||
|
>
|
||||||
|
</ngx-whats-new>
|
||||||
+90
-36
@@ -7,8 +7,16 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
|||||||
import { ChannelStore } from './state';
|
import { ChannelStore } from './state';
|
||||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||||
import { Settings } from './settings/settings.interface';
|
import { Settings } from './settings/settings.interface';
|
||||||
import { EPG_ERROR, EPG_FETCH, EPG_FETCH_DONE } from '../../ipc-commands';
|
import {
|
||||||
|
EPG_ERROR,
|
||||||
|
EPG_FETCH,
|
||||||
|
EPG_FETCH_DONE,
|
||||||
|
SHOW_WHATS_NEW,
|
||||||
|
} from '../../ipc-commands';
|
||||||
import { SettingsService } from './services/settings.service';
|
import { SettingsService } from './services/settings.service';
|
||||||
|
import { WhatsNewService } from './services/whats-new.service';
|
||||||
|
import * as semver from 'semver';
|
||||||
|
import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface';
|
||||||
|
|
||||||
// create custom title bar
|
// create custom title bar
|
||||||
new Titlebar({
|
new Titlebar({
|
||||||
@@ -23,6 +31,16 @@ new Titlebar({
|
|||||||
styleUrls: ['./app.component.scss'],
|
styleUrls: ['./app.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
/** Visibility flag of the "what is new" modal dialog */
|
||||||
|
isDialogVisible$ = this.whatsNewService.dialogState$;
|
||||||
|
/** Dialog options */
|
||||||
|
options = this.whatsNewService.options;
|
||||||
|
/** Modals to show for the updated version of the application */
|
||||||
|
modals: ModalWindow[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of AppComponent
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private channelStore: ChannelStore,
|
private channelStore: ChannelStore,
|
||||||
private electronService: ElectronService,
|
private electronService: ElectronService,
|
||||||
@@ -31,24 +49,37 @@ export class AppComponent {
|
|||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private snackBar: MatSnackBar,
|
private snackBar: MatSnackBar,
|
||||||
private storage: StorageMap
|
private storage: StorageMap,
|
||||||
|
private whatsNewService: WhatsNewService
|
||||||
) {
|
) {
|
||||||
this.translate.setDefaultLang('en');
|
this.translate.setDefaultLang('en');
|
||||||
|
|
||||||
if (electronService.isElectron) {
|
this.setRendererListeners();
|
||||||
console.log(process.env);
|
|
||||||
console.log('Run in electron');
|
if (
|
||||||
console.log(
|
(this.electronService.remote.process.platform === 'linux' ||
|
||||||
'Electron ipcRenderer',
|
this.electronService.remote.process.platform === 'win32') &&
|
||||||
this.electronService.ipcRenderer
|
this.electronService.remote.process.argv.length > 2
|
||||||
|
) {
|
||||||
|
const filePath = this.electronService.remote.process.argv.find(
|
||||||
|
(filepath) =>
|
||||||
|
filepath.endsWith('.m3u') || filepath.endsWith('.m3u8')
|
||||||
);
|
);
|
||||||
console.log(
|
if (filePath) {
|
||||||
'NodeJS childProcess',
|
const filePathsArray = filePath.split('/');
|
||||||
this.electronService.childProcess
|
const fileName = filePathsArray[filePathsArray.length - 1];
|
||||||
);
|
this.electronService.ipcRenderer.send('open-file', {
|
||||||
} else {
|
filePath,
|
||||||
console.log('Run in browser');
|
fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes all necessary listeners for the events from the renderer process
|
||||||
|
*/
|
||||||
|
setRendererListeners(): void {
|
||||||
this.electronService.ipcRenderer.on('add-playlist-view', () => {
|
this.electronService.ipcRenderer.on('add-playlist-view', () => {
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
this.router.navigateByUrl('/', { skipLocationChange: true });
|
this.router.navigateByUrl('/', { skipLocationChange: true });
|
||||||
@@ -73,6 +104,7 @@ export class AppComponent {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.electronService.ipcRenderer.on(EPG_ERROR, () => {
|
this.electronService.ipcRenderer.on(EPG_ERROR, () => {
|
||||||
this.snackBar.open('EPG Error: something went wrong...', null, {
|
this.snackBar.open('EPG Error: something went wrong...', null, {
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
@@ -81,24 +113,14 @@ export class AppComponent {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
this.electronService.ipcRenderer.on(SHOW_WHATS_NEW, () => {
|
||||||
(this.electronService.remote.process.platform === 'linux' ||
|
this.ngZone.run(() => {
|
||||||
this.electronService.remote.process.platform === 'win32') &&
|
this.modals = this.whatsNewService.getModalsByVersion(
|
||||||
this.electronService.remote.process.argv.length > 2
|
this.electronService.getAppVersion()
|
||||||
) {
|
);
|
||||||
const filePath = this.electronService.remote.process.argv.find(
|
this.setDialogVisibility(true);
|
||||||
(filepath) =>
|
});
|
||||||
filepath.endsWith('.m3u') || filepath.endsWith('.m3u8')
|
});
|
||||||
);
|
|
||||||
if (filePath) {
|
|
||||||
const filePathsArray = filePath.split('/');
|
|
||||||
const fileName = filePathsArray[filePathsArray.length - 1];
|
|
||||||
this.electronService.ipcRenderer.send('open-file', {
|
|
||||||
filePath,
|
|
||||||
fileName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,10 +146,42 @@ export class AppComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// store current app version (TODO: "what is new" dialog in v0.5)
|
this.handleWhatsNewDialog();
|
||||||
this.storage
|
}
|
||||||
.set('version', this.electronService.getAppVersion())
|
|
||||||
.subscribe(() => {});
|
/**
|
||||||
|
* Checks the actual version of the application and shows the "what is new" dialog if the updated version was detected
|
||||||
|
*/
|
||||||
|
handleWhatsNewDialog(): void {
|
||||||
|
const actualVersion = this.electronService.getAppVersion();
|
||||||
|
this.storage.get('version').subscribe((version) => {
|
||||||
|
const isNewVersion = semver.gt(actualVersion, version || '0.0.0');
|
||||||
|
if (!version || isNewVersion) {
|
||||||
|
this.modals = this.whatsNewService.getModalsByVersion(
|
||||||
|
actualVersion
|
||||||
|
);
|
||||||
|
this.setDialogVisibility(true);
|
||||||
|
}
|
||||||
|
this.setVersion(actualVersion);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the version of the application in localstorage
|
||||||
|
* @param version actual version of the application
|
||||||
|
*/
|
||||||
|
setVersion(version: string): void {
|
||||||
|
this.storage.set('version', version).subscribe(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility flag of the modal window
|
||||||
|
* @param visible show/hide window flag
|
||||||
|
*/
|
||||||
|
setDialogVisibility(visible: boolean): void {
|
||||||
|
if (this.modals.length > 0) {
|
||||||
|
this.whatsNewService.changeDialogVisibleState(visible);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import '../polyfills';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { HttpClientModule, HttpClient } from '@angular/common/http';
|
import { HttpClientModule, HttpClient } from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { NgxWhatsNewModule } from 'ngx-whats-new';
|
||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
|
|
||||||
// NG Translate
|
// NG Translate
|
||||||
@@ -26,8 +26,8 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
|||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
CommonModule,
|
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
|
NgxWhatsNewModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
import { WhatsNewService } from './whats-new.service';
|
||||||
|
|
||||||
|
describe('Service: WhatsNew', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [WhatsNewService],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ...', inject([WhatsNewService], (service: WhatsNewService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should change the state', inject(
|
||||||
|
[WhatsNewService],
|
||||||
|
(service: WhatsNewService) => {
|
||||||
|
spyOn(service.dialogState$, 'next');
|
||||||
|
service.changeDialogVisibleState(true);
|
||||||
|
expect(service.dialogState$.next).toHaveBeenCalledTimes(1);
|
||||||
|
expect(service.dialogState$.next).toHaveBeenCalledWith(true);
|
||||||
|
service.changeDialogVisibleState(false);
|
||||||
|
expect(service.dialogState$.next).toHaveBeenCalledTimes(2);
|
||||||
|
expect(service.dialogState$.next).toHaveBeenCalledWith(false);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
it('should return modal windows for the requested version ', inject(
|
||||||
|
[WhatsNewService],
|
||||||
|
(service: WhatsNewService) => {
|
||||||
|
const version = '0.6.0';
|
||||||
|
const modals = service.getModalsByVersion(version);
|
||||||
|
expect(modals).toBeDefined();
|
||||||
|
expect(modals).toHaveLength(service.modals[version].length);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
});
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class WhatsNewService {
|
||||||
|
/** Visibility state of the modal */
|
||||||
|
dialogState$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/** Array with all available modals */
|
||||||
|
modals = {
|
||||||
|
'0.6.0': [
|
||||||
|
{
|
||||||
|
imageHeight: 470,
|
||||||
|
imageBgColor: '#333',
|
||||||
|
imageSrc: './assets/updates/060/dark-theme.png',
|
||||||
|
title: 'New in v0.6.0 - Dark theme 🎉',
|
||||||
|
text:
|
||||||
|
'Finally, the dark theme is now available. Check the settings page of the application to change the theme.',
|
||||||
|
button: {
|
||||||
|
text: 'OKAY',
|
||||||
|
textColor: '#ccc',
|
||||||
|
bgColor: '#111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
imageHeight: 470,
|
||||||
|
imageBgColor: '#333',
|
||||||
|
imageSrc: './assets/updates/060/channel-logos.png',
|
||||||
|
title: 'Channel logos 📺',
|
||||||
|
text:
|
||||||
|
'The channel list was extended with the visualization of logotypes (only visible if they are defined in the playlist)',
|
||||||
|
button: {
|
||||||
|
text: 'OKAY',
|
||||||
|
textColor: '#ccc',
|
||||||
|
bgColor: '#111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
imageHeight: 470,
|
||||||
|
imageBgColor: '#333',
|
||||||
|
imageSrc: './assets/updates/060/custom-user-agent.png',
|
||||||
|
title: 'Custom user agent 🕵️♂️',
|
||||||
|
text:
|
||||||
|
'Some IPTV providers need a specific user-agent in order to play their playlist. Support of custom user agent is available now.',
|
||||||
|
button: {
|
||||||
|
text: 'GOT IT',
|
||||||
|
textColor: '#ccc',
|
||||||
|
bgColor: '#111',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Options for the "what is new" modal dialogs */
|
||||||
|
options = {
|
||||||
|
width: '500px',
|
||||||
|
customStyle: {
|
||||||
|
boxShadow: '0px 0px 10px 5px #111',
|
||||||
|
backgroundColor: '#333',
|
||||||
|
textColor: '#eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the visibility state of the modal dialog
|
||||||
|
* @param value flag to set
|
||||||
|
*/
|
||||||
|
changeDialogVisibleState(value: boolean): void {
|
||||||
|
this.dialogState$.next(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with modals for the provided version of the application
|
||||||
|
* @param version version string
|
||||||
|
*/
|
||||||
|
getModalsByVersion(version: string): ModalWindow[] {
|
||||||
|
return this.modals[version] || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Reference in new issue
Block a user