Merge pull request #625 from 4gray/app-restructuring

refactor(nx): add restructuring
This commit is contained in:
4gray authored and GitHub committed 2025-10-13 01:34:10 +02:00
commit af55372655
42 files changed
+215 -193

No files matched your search

@@ -3,8 +3,8 @@ import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electron', {
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
platform: process.platform,
fetchPlaylistByUrl: (url: string) =>
ipcRenderer.invoke('fetch-playlist-by-url', url),
fetchPlaylistByUrl: (url: string, title?: string) =>
ipcRenderer.invoke('fetch-playlist-by-url', url, title),
updatePlaylistFromFilePath: (filePath: string, title: string) =>
ipcRenderer.invoke('update-playlist-from-file-path', filePath, title),
openPlaylistFromFile: () => ipcRenderer.invoke('open-playlist-from-file'),
@@ -17,7 +17,7 @@ export default class PlaylistEvents {
const https = require('https');
ipcMain.handle('fetch-playlist-by-url', async (event, url) => {
ipcMain.handle('fetch-playlist-by-url', async (event, url, title?: string) => {
try {
const agent = new https.Agent({
rejectUnauthorized: false,
@@ -33,7 +33,7 @@ ipcMain.handle('fetch-playlist-by-url', async (event, url) => {
: extractedName;
const playlistObject = createPlaylistObject(
playlistName,
title ?? playlistName,
parsedPlaylist,
url,
'URL'
@@ -62,7 +62,6 @@ ipcMain.handle(
);
ipcMain.handle('open-playlist-from-file', async () => {
// 1. Await the result from the dialog
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
@@ -89,13 +88,9 @@ ipcMain.handle('open-playlist-from-file', async () => {
'FILE'
);
console.log('Sending playlist object to renderer:', playlistObject);
// 5. Return the final object. This value is sent back to Angular.
return playlistObject;
} catch (error) {
console.error('Error reading or parsing the file:', error);
// It's good practice to throw the error so the renderer can catch it
throw new Error('Failed to process the selected file.');
}
});
+35 -17
View File
@@ -1,6 +1,7 @@
import { inject, Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Params } from '@angular/router';
import { DataService } from '@iptvnator/services';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { readTextFile } from '@tauri-apps/plugin-fs';
@@ -19,7 +20,6 @@ import {
PLAYLIST_UPDATE_RESPONSE,
XTREAM_RESPONSE,
} from 'shared-interfaces';
import { DataService } from '../../../../../libs/services/src/lib/data.service';
import { AppConfig } from '../../environments/environment';
@Injectable({
@@ -58,11 +58,11 @@ export class TauriService extends DataService {
if (type === PLAYLIST_PARSE_BY_URL) {
this.fetchM3uPlaylistFromUrl(payload);
} else if (type === PLAYLIST_UPDATE) {
// TODO: update playlist from URL?
this.updateM3uPlaylistFromFile(
payload as {
id: string;
filePath: string;
filePath?: string;
url?: string;
title: string;
}
);
@@ -201,24 +201,42 @@ export class TauriService extends DataService {
private async updateM3uPlaylistFromFile(data: {
id: string;
filePath: string;
url?: string;
filePath?: string;
title: string;
}) {
window.electron
.updatePlaylistFromFilePath(data.filePath, data.title)
.then((playlistObject) => {
window.postMessage({
type: PLAYLIST_UPDATE_RESPONSE,
payload: {
message:
'Success! The playlist was successfully updated',
playlist: {
...playlistObject,
_id: data.id,
},
let methodToCall = null;
if (data.url && !data.filePath) {
// fetch from url
methodToCall = window.electron.fetchPlaylistByUrl(
data.url,
data.title
);
} else if (data.filePath && !data.url) {
// update from file path
methodToCall = window.electron.updatePlaylistFromFilePath(
data.filePath,
data.title
);
} else {
console.error(
'Either url or filePath must be provided, but not both.'
);
return;
}
methodToCall.then((playlistObject) => {
window.postMessage({
type: PLAYLIST_UPDATE_RESPONSE,
payload: {
message: 'Success! The playlist was successfully updated',
playlist: {
...playlistObject,
_id: data.id,
},
});
},
});
});
}
/* private getErrorMessageByStatusCode(status: number) {
@@ -1,4 +1,4 @@
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
@@ -7,7 +7,11 @@ import { TranslateModule } from '@ngx-translate/core';
imports: [MatIconModule, TranslateModule],
template: `
<div mat-dialog-content>
<img src="./assets/icons/icon-tv-256.png" width="128" /><br />
<img
src="./assets/icons/icon-tv-256.png"
width="128"
alt="logo"
/><br />
<h2 mat-dialog-title>{{ 'ABOUT.TITLE' | translate }}</h2>
<p>{{ 'ABOUT.DESCRIPTION' | translate }}</p>
<p>{{ 'ABOUT.VERSION' | translate }}: {{ appVersion }}</p>
@@ -19,7 +23,8 @@ import { TranslateModule } from '@ngx-translate/core';
[attr.aria-label]="'ABOUT.GITHUB_TOOLTIP' | translate"
><img
src="./assets/icons/github-light.png"
[title]="'ABOUT.GITHUB_TOOLTIP' | translate" /></a
[title]="'ABOUT.GITHUB_TOOLTIP' | translate"
alt="GitHub" /></a
>&nbsp;
<a
href="http://twitter.com/share?text=IPTVnator &mdash; free cross-platform IPTV player. Available as PWA and as native application.&url=https://github.com/4gray/iptvnator&hashtags=iptv,m3u,video-player"
@@ -29,6 +34,7 @@ import { TranslateModule } from '@ngx-translate/core';
height="32"
src="./assets/icons/twitter-light.png"
[title]="'ABOUT.TWITTER_TOOLTIP' | translate"
alt="Twitter"
/>
</a>
<a
@@ -57,8 +63,8 @@ import { TranslateModule } from '@ngx-translate/core';
font-size: 32px;
}
`,
]
],
})
export class AboutDialogComponent {
constructor(@Inject(MAT_DIALOG_DATA) public appVersion: string) {}
public appVersion = inject<string>(MAT_DIALOG_DATA);
}
@@ -1,4 +1,4 @@
import { Component, inject, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
MAT_DIALOG_DATA,
@@ -22,14 +22,14 @@ export type PlaylistType = 'xtream' | 'url' | 'text' | 'file' | 'stalker';
@Component({
imports: [
FileUploadComponent,
MatButtonModule,
MatDialogModule,
TranslateModule,
FileUploadComponent,
XtreamCodeImportComponent,
StalkerPortalImportComponent,
TextImportComponent,
TranslateModule,
UrlUploadComponent,
XtreamCodeImportComponent,
],
selector: 'app-add-playlist',
templateUrl: './add-playlist-dialog.component.html',
@@ -40,11 +40,12 @@ export class AddPlaylistDialogComponent {
private store = inject(Store);
private snackBar = inject(MatSnackBar);
private translateService = inject(TranslateService);
readonly data = inject<{ type: PlaylistType }>(MAT_DIALOG_DATA);
readonly playlistType!: PlaylistType;
constructor(@Inject(MAT_DIALOG_DATA) data: { type: PlaylistType }) {
this.playlistType = data.type;
constructor() {
this.playlistType = this.data.type;
}
/**
@@ -1,7 +0,0 @@
export interface ConfirmDialogData {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
onConfirm: () => void;
}
@@ -65,10 +65,11 @@ import { PortalStore } from '../../../xtream/portal.store';
MatIconModule,
RouterLink,
TranslateModule,
]
],
})
export class ExternalPlayerInfoDialogComponent {
portalStore = inject(PortalStore);
readonly portalStore = inject(PortalStore);
openUrl(url: string) {
window.open(url, '_blank');
}
@@ -9,16 +9,16 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute, Router } from '@angular/router';
import {
DataService,
SortBy,
SortOrder,
SortService,
} from '@iptvnator/services';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { open } from '@tauri-apps/plugin-shell';
import { selectActiveTypeFilters, setSelectedFilters } from 'm3u-state';
import { DataService } from '../../../../../../../libs/services/src/lib/data.service';
import {
SortBy,
SortOrder,
SortService,
} from '../../../../../../../libs/services/src/lib/sort.service';
import { HomeComponent } from '../../../home/home.component';
import { AboutDialogComponent } from '../about-dialog/about-dialog.component';
import {
@@ -1,4 +1,4 @@
import { StalkerPortalActions } from '../../shared/stalker-portal-actions.enum';
import { StalkerPortalActions } from 'shared-interfaces';
export const StalkerContentTypes = {
stb: {
@@ -4,12 +4,11 @@ import { MatCardModule } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { ActivatedRoute } from '@angular/router';
import { DataService } from '@iptvnator/services';
import { Store } from '@ngrx/store';
import { TranslatePipe } from '@ngx-translate/core';
import { selectPlaylistById } from 'm3u-state';
import { STALKER_REQUEST } from 'shared-interfaces';
import { DataService } from '../../../../../../libs/services/src/lib/data.service';
import { StalkerPortalActions } from '../../../shared/stalker-portal-actions.enum';
import { STALKER_REQUEST, StalkerPortalActions } from 'shared-interfaces';
import { SearchFormComponent } from '../../shared/components/search-form/search-form.component';
import { SearchResultItemComponent } from '../../shared/components/search-result-item/search-result-item.component';
import { PlaylistErrorViewComponent } from '../../xtream-tauri/playlist-error-view/playlist-error-view.component';
+6 -4
View File
@@ -1,5 +1,6 @@
import { computed, inject, resource, signal } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { DataService, PlaylistsService } from '@iptvnator/services';
import {
patchState,
signalStore,
@@ -9,10 +10,11 @@ import {
withState,
} from '@ngrx/signals';
import { TranslateService } from '@ngx-translate/core';
import { PlaylistMeta, STALKER_REQUEST } from 'shared-interfaces';
import { DataService } from '../../../../../libs/services/src/lib/data.service';
import { PlaylistsService } from '../../../../../libs/services/src/lib/playlists.service';
import { StalkerPortalActions } from '../../shared/stalker-portal-actions.enum';
import {
PlaylistMeta,
STALKER_REQUEST,
StalkerPortalActions,
} from 'shared-interfaces';
import { PlayerService } from '../services/player.service';
import { ContentType } from '../xtream/content-type.enum';
import { StalkerSeason } from './models/stalker-season.interface';
@@ -6,7 +6,7 @@ import {
} from '@angular/core';
import { MatListModule } from '@angular/material/list';
import { TranslatePipe } from '@ngx-translate/core';
import { XtreamCategory } from '../../../shared/xtream-category.interface';
import { XtreamCategory } from 'shared-interfaces';
import { PlaylistErrorViewComponent } from '../playlist-error-view/playlist-error-view.component';
@Component({
@@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { selectActivePlaylist } from 'm3u-state';
import { BehaviorSubject, switchMap } from 'rxjs';
import { XtreamCategory } from '../../../shared/xtream-category.interface';
import { XtreamCategory } from 'shared-interfaces';
import { FavoritesLayoutComponent } from '../../shared/components/favorites-layout/favorites-layout.component';
import { FavoriteItem } from '../services/favorite-item.interface';
import { FavoritesService } from '../services/favorites.service';
@@ -12,7 +12,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { ActivatedRoute } from '@angular/router';
import { TranslatePipe } from '@ngx-translate/core';
import { XtreamCategory } from '../../../shared/xtream-category.interface';
import { XtreamCategory } from 'shared-interfaces';
import { EpgViewComponent, WebPlayerViewComponent } from 'shared-portals';
import { SettingsStore } from '../../services/settings-store.service';
import { MpvPlayerBarComponent } from '../../shared/components/mpv-player-bar/mpv-player-bar.component';
@@ -24,8 +24,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { ActivatedRoute } from '@angular/router';
import { FilterPipe } from '@iptvnator/pipes';
import { TranslatePipe } from '@ngx-translate/core';
import { XtreamItem } from '../../../../../../libs/shared/interfaces/src/lib/xtream-item.interface';
import { XtreamCategory } from '../../../shared/xtream-category.interface';
import { XtreamCategory, XtreamItem } from 'shared-interfaces';
import { FavoritesService } from '../services/favorites.service';
import { XtreamStore } from '../xtream.store';
@@ -3,7 +3,7 @@ import { Component, EventEmitter, Output, input } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon';
import { XtreamSerieEpisode } from '../../../shared/xtream-serie-details.interface';
import { XtreamSerieEpisode } from 'shared-interfaces';
@Component({
selector: 'app-season-container',
@@ -3,7 +3,7 @@ import { MatButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { ActivatedRoute } from '@angular/router';
import { TranslatePipe } from '@ngx-translate/core';
import { XtreamSerieEpisode } from '../../../shared/xtream-serie-details.interface';
import { XtreamSerieEpisode } from 'shared-interfaces';
import { SeasonContainerComponent } from '../season-container/season-container.component';
import { XtreamStore } from '../xtream.store';
@@ -4,7 +4,7 @@ import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { ActivatedRoute } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { XtreamVodDetails } from '../../../shared/xtream-vod-details.interface';
import { XtreamVodDetails } from 'shared-interfaces';
import { SettingsStore } from '../../services/settings-store.service';
import { XtreamStore } from '../xtream.store';
import { SafePipe } from './safe.pipe';
@@ -1,12 +1,11 @@
import { Component, inject, OnInit } from '@angular/core';
import { CategoryViewComponent } from './category-view/category-view.component';
import { MatIconButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { XtreamCategory } from '../../shared/xtream-category.interface';
import { XtreamCategory } from 'shared-interfaces';
import { MpvPlayerBarComponent } from '../shared/components/mpv-player-bar/mpv-player-bar.component';
import { CategoryViewComponent } from './category-view/category-view.component';
import { XtreamStore } from './xtream.store';
@Component({
@@ -1,9 +1,11 @@
import { GlobalSearchResult } from '../../../../../libs/services/src/lib/database.service';
import { EpgItem } from '../../../../../libs/shared/interfaces/src/lib/epg-item.interface';
import { XtreamSerieItem } from '../../../../../libs/shared/interfaces/src/lib/xtream-serie-item.interface';
import { XtreamCategory } from '../../shared/xtream-category.interface';
import { XtreamLiveStream } from '../../shared/xtream-live-stream.interface';
import { XtreamVodStream } from '../../shared/xtream-vod-stream.interface';
import { GlobalSearchResult } from '@iptvnator/services';
import {
EpgItem,
XtreamCategory,
XtreamLiveStream,
XtreamSerieItem,
XtreamVodStream,
} from 'shared-interfaces';
export type ContentType = 'live' | 'vod' | 'series';
+29 -24
View File
@@ -1,5 +1,6 @@
import { computed, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DatabaseService, DataService } from '@iptvnator/services';
import {
patchState,
signalStore,
@@ -20,15 +21,13 @@ import {
switchMap,
tap,
} from 'rxjs';
import { Playlist } from 'shared-interfaces';
import { DataService } from '../../../../../libs/services/src/lib/data.service';
import { DatabaseService } from '../../../../../libs/services/src/lib/database.service';
import { XtreamCodeActions } from '../../shared/xtream-code-actions';
import {
Playlist,
XtreamCodeActions,
XtreamSerieDetails,
XtreamSerieEpisode,
} from '../../shared/xtream-serie-details.interface';
import { XtreamVodDetails } from '../../shared/xtream-vod-details.interface';
XtreamVodDetails,
} from 'shared-interfaces';
import { PlayerService } from '../services/player.service';
import { SettingsStore } from '../services/settings-store.service';
import { XtreamAccountInfo } from './account-info/account-info.interface';
@@ -85,8 +84,8 @@ export const XtreamStore = signalStore(
return type === 'live'
? store.liveCategories()
: type === 'vod'
? store.vodCategories()
: store.serialCategories();
? store.vodCategories()
: store.serialCategories();
}),
getSelectedCategory: computed(() => {
const categoryId = store.selectedCategoryId();
@@ -103,8 +102,8 @@ export const XtreamStore = signalStore(
categoryType === 'live'
? store.liveStreams()
: categoryType === 'vod'
? store.vodStreams()
: store.serialStreams();
? store.vodStreams()
: store.serialStreams();
if (!store.selectedItem()) return null;
@@ -129,8 +128,8 @@ export const XtreamStore = signalStore(
categoryType === 'live'
? store.liveStreams()
: categoryType === 'vod'
? store.vodStreams()
: store.serialStreams();
? store.vodStreams()
: store.serialStreams();
return content
.filter((item) => item.category_id === categoryId)
@@ -149,13 +148,13 @@ export const XtreamStore = signalStore(
.filter((i) => Number((i as any).id) === categoryId)
.length
: categoryType === 'vod'
? store
.vodStreams()
.filter((i) => Number(i.category_id) === categoryId)
.length
: store
.serialStreams()
.filter((i) => i.category_id === categoryId).length;
? store
.vodStreams()
.filter((i) => Number(i.category_id) === categoryId)
.length
: store
.serialStreams()
.filter((i) => i.category_id === categoryId).length;
return Math.ceil(totalItems / store.limit());
}),
@@ -166,8 +165,8 @@ export const XtreamStore = signalStore(
categoryType === 'live'
? store.liveStreams()
: categoryType === 'vod'
? store.vodStreams()
: store.serialStreams();
? store.vodStreams()
: store.serialStreams();
return content.filter((item) => item.category_id === categoryId);
}),
@@ -223,7 +222,9 @@ export const XtreamStore = signalStore(
}
const remoteData = await dataService.fetchData(
`${store.currentPlaylist().serverUrl}/player_api.php`,
`${
store.currentPlaylist().serverUrl
}/player_api.php`,
queryParams
);
@@ -293,7 +294,9 @@ export const XtreamStore = signalStore(
}
const remoteData = await dataService.fetchData(
`${store.currentPlaylist().serverUrl}/player_api.php`,
`${
store.currentPlaylist().serverUrl
}/player_api.php`,
queryParams
);
@@ -758,7 +761,9 @@ export const XtreamStore = signalStore(
async loadChannelEpg(streamId: number) {
try {
const response = await dataService.fetchData(
`${store.currentPlaylist().serverUrl}/player_api.php`,
`${
store.currentPlaylist().serverUrl
}/player_api.php`,
{
action: 'get_short_epg',
username: store.currentPlaylist().username,
@@ -1,5 +1,4 @@
import { StalkerPortalActions } from '../../shared/stalker-portal-actions.enum';
import { XtreamCodeActions } from '../../shared/xtream-code-actions';
import { StalkerPortalActions, XtreamCodeActions } from 'shared-interfaces';
export type PortalActions = StalkerPortalActions | XtreamCodeActions;
@@ -3,53 +3,43 @@ import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { FilterPipe } from '@iptvnator/pipes';
import { TranslateModule } from '@ngx-translate/core';
import { XtreamCategory } from '../../../shared/xtream-category.interface';
import { XtreamCategory } from 'shared-interfaces';
import { PlaylistErrorViewComponent } from '../playlist-error-view/playlist-error-view.component';
import { PortalStore } from '../portal.store';
@Component({
selector: 'app-category-view',
template: `
@if (items?.length > 0) {
@for (
item of items() | filterBy: searchPhrase() : 'category_name';
track $index
) {
<mat-card
appearance="outlined"
class="category-item"
(click)="categoryClicked.emit(item)"
>
<mat-card-content>
{{
item.category_name ||
item.name ||
'No category name'
}}
</mat-card-content>
</mat-card>
}
@if (
!(items() | filterBy: searchPhrase() : 'category_name')?.length
) {
<app-playlist-error-view
title="No results"
[description]="
'PORTALS.EMPTY_LIST_VIEW.NO_SEARCH_RESULTS' | translate
"
[showActionButtons]="false"
[viewType]="'NO_SEARCH_RESULTS'"
/>
}
} @else {
<app-playlist-error-view
[title]="'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.TITLE' | translate"
[description]="
'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.DESCRIPTION' | translate
"
[showActionButtons]="false"
[viewType]="'EMPTY_CATEGORY'"
/>
@if (items?.length > 0) { @for ( item of items() | filterBy:
searchPhrase() : 'category_name'; track $index ) {
<mat-card
appearance="outlined"
class="category-item"
(click)="categoryClicked.emit(item)"
>
<mat-card-content>
{{ item.category_name || item.name || 'No category name' }}
</mat-card-content>
</mat-card>
} @if ( !(items() | filterBy: searchPhrase() : 'category_name')?.length
) {
<app-playlist-error-view
title="No results"
[description]="
'PORTALS.EMPTY_LIST_VIEW.NO_SEARCH_RESULTS' | translate
"
[showActionButtons]="false"
[viewType]="'NO_SEARCH_RESULTS'"
/>
} } @else {
<app-playlist-error-view
[title]="'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.TITLE' | translate"
[description]="
'PORTALS.ERROR_VIEW.EMPTY_CATEGORY.DESCRIPTION' | translate
"
[showActionButtons]="false"
[viewType]="'EMPTY_CATEGORY'"
/>
}
`,
styleUrl: './category-view.component.scss',
@@ -29,7 +29,7 @@ import {
distinctUntilChanged,
map,
} from 'rxjs';
import { XtreamLiveStream } from '../../../shared/xtream-live-stream.interface';
import { XtreamLiveStream } from 'shared-interfaces';
import { SettingsComponent } from '../../settings/settings.component';
import { SettingsStore } from '../../services/settings-store.service';
@@ -1,7 +1,7 @@
import { KeyValuePipe } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { XtreamSerieEpisode } from '../../../shared/xtream-serie-details.interface';
import { XtreamSerieEpisode } from 'shared-interfaces';
@Component({
selector: 'app-season-container',
@@ -2,14 +2,11 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { ActivatedRoute } from '@angular/router';
import { PlaylistsService } from '@iptvnator/services';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { selectCurrentPlaylist } from 'm3u-state';
import { PlaylistsService } from '../../../../../../libs/services/src/lib/playlists.service';
import {
XtreamSerieDetails,
XtreamSerieEpisode,
} from '../../../shared/xtream-serie-details.interface';
import { XtreamSerieDetails, XtreamSerieEpisode } from 'shared-interfaces';
import { SeasonContainerComponent } from '../season-container/season-container.component';
@Component({
@@ -10,9 +10,9 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { ActivatedRoute } from '@angular/router';
import { PlaylistsService } from '@iptvnator/services';
import { TranslatePipe } from '@ngx-translate/core';
import { PlaylistsService } from '../../../../../../libs/services/src/lib/playlists.service';
import { XtreamVodDetails } from '../../../shared/xtream-vod-details.interface';
import { XtreamVodDetails } from 'shared-interfaces';
import { SafePipe } from './safe.pipe';
@Component({
@@ -14,7 +14,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar } from '@angular/material/snack-bar';
import { DataService } from '@iptvnator/services';
import { DataService, PlaylistsService } from '@iptvnator/services';
import { Store } from '@ngrx/store';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { selectCurrentPlaylist } from 'm3u-state';
@@ -30,18 +30,15 @@ import {
VideoPlayer,
XTREAM_REQUEST,
XTREAM_RESPONSE,
} from 'shared-interfaces';
import { LiveStreamLayoutComponent } from 'shared-portals';
import { PlaylistsService } from '../../../../../libs/services/src/lib/playlists.service';
import { XtreamCategory } from '../../shared/xtream-category.interface';
import { XtreamCodeActions } from '../../shared/xtream-code-actions';
import { XtreamLiveStream } from '../../shared/xtream-live-stream.interface';
import { XtreamResponse } from '../../shared/xtream-response.interface';
import {
XtreamCategory,
XtreamCodeActions,
XtreamLiveStream,
XtreamResponse,
XtreamSerieDetails,
XtreamSerieEpisode,
} from '../../shared/xtream-serie-details.interface';
import { XtreamVodDetails } from '../../shared/xtream-vod-details.interface';
XtreamVodDetails,
} from 'shared-interfaces';
import { LiveStreamLayoutComponent } from 'shared-portals';
import { SettingsStore } from '../services/settings-store.service';
import { ExternalPlayerInfoDialogComponent } from '../shared/components/external-player-info-dialog/external-player-info-dialog.component';
import { PlaylistErrorViewComponent } from '../xtream/playlist-error-view/playlist-error-view.component';
+4 -1
View File
@@ -12,7 +12,10 @@ declare global {
electron: {
getAppVersion: () => Promise<string>;
platform: string;
fetchPlaylistByUrl: (url: string) => Promise<Playlist>;
fetchPlaylistByUrl: (
url: string,
title?: string
) => Promise<Playlist>;
updatePlaylistFromFilePath: (
filePath: string,
title: string
+1 -2
View File
@@ -1,7 +1,6 @@
import { inject, Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogData } from '../../../../apps/web/src/app/shared/components/confirm-dialog/confirm-dialog-data.interface';
import { ConfirmDialogComponent } from '../../../../apps/web/src/app/shared/components/confirm-dialog/confirm-dialog.component';
import { ConfirmDialogComponent, ConfirmDialogData } from 'components';
@Injectable({
providedIn: 'root',
+8
View File
@@ -12,8 +12,16 @@ export * from './lib/parsed-playlist.interface';
export * from './lib/playlist-meta.type';
export * from './lib/playlist.interface';
export * from './lib/settings.interface';
export * from './lib/stalker-portal-actions.enum';
export * from './lib/store-keys.enum';
export * from './lib/stream-format.enum';
export * from './lib/theme.enum';
export * from './lib/xtream-category.interface';
export * from './lib/xtream-code-actions';
export * from './lib/xtream-item.interface';
export * from './lib/xtream-live-stream.interface';
export * from './lib/xtream-response.interface';
export * from './lib/xtream-serie-details.interface';
export * from './lib/xtream-serie-item.interface';
export * from './lib/xtream-vod-details.interface';
export * from './lib/xtream-vod-stream.interface';
@@ -1,4 +1,4 @@
import { XtreamItem } from '../../../../libs/shared/interfaces/src/lib/xtream-item.interface';
import { XtreamItem } from 'shared-interfaces';
export interface XtreamLiveStream extends XtreamItem {
stream_type: 'live';
@@ -1,4 +1,4 @@
import { XtreamItem } from '../../../../libs/shared/interfaces/src/lib/xtream-item.interface';
import { XtreamItem } from 'shared-interfaces';
export interface XtreamVodStream extends XtreamItem {
stream_type: 'movie';
+1
View File
@@ -1,6 +1,7 @@
export * from './lib/art-player/art-player.component';
export * from './lib/audio-player/audio-player.component';
export * from './lib/channel-list-container/channel-list-container.component';
export * from './lib/confirm-dialog/confirm-dialog.component';
export * from './lib/epg-list/epg-item-description/epg-item-description.component';
export * from './lib/epg-list/epg-list-item/epg-list-item.component';
export * from './lib/epg-list/epg-list.component';
@@ -1,8 +1,16 @@
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { ConfirmDialogData } from './confirm-dialog-data.interface';
export interface ConfirmDialogData {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
onConfirm: () => void;
}
@Component({
imports: [MatButtonModule, MatDialogModule, TranslateModule],
template: `
@@ -14,19 +22,19 @@ import { ConfirmDialogData } from './confirm-dialog-data.interface';
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close cdkFocusInitial color="accent">
{{ dialogData?.cancelLabel || 'NO' | translate }}
{{ dialogData.cancelLabel || 'NO' | translate }}
</button>
<button mat-flat-button [mat-dialog-close]="true" color="accent">
{{ dialogData?.confirmLabel || 'YES' | translate }}
{{ dialogData.confirmLabel || 'YES' | translate }}
</button>
</mat-dialog-actions>
`
`,
})
export class ConfirmDialogComponent {
/** Contains meta information to show in the dialog */
dialogData!: ConfirmDialogData;
readonly dialogData!: ConfirmDialogData;
readonly data = inject<ConfirmDialogData>(MAT_DIALOG_DATA);
constructor(@Inject(MAT_DIALOG_DATA) data: ConfirmDialogData) {
this.dialogData = data;
constructor() {
this.dialogData = this.data;
}
}
@@ -26,11 +26,13 @@
<mat-list-option
*cdkVirtualFor="
let item of channels
| filterBy: searchString() : 'name';
| filterBy : searchString() : 'name';
trackBy: trackBy;
templateCacheSize: 0
"
[selected]="item.stream_id === activeLiveStream?.stream_id"
[selected]="
item.stream_id === activeLiveStream?.stream_id
"
[value]="item"
(click)="itemClicked.emit(item)"
>
@@ -39,6 +41,7 @@
class="stream-icon"
*ngIf="item.stream_icon"
[src]="item.stream_icon"
alt="channel icon"
/>
<div class="title" matListItemTitle>
{{ item.name }}
@@ -48,20 +51,17 @@
</mat-selection-list>
</div>
</div>
@if (player === 'videojs' || player === 'html5') {
@if (streamUrl) {
<div class="video-player">
<app-web-player-view [streamUrl]="streamUrl" />
</div>
}
}
@if (streamUrl) {
<div class="epg">
<div class="epg-content">
<app-epg-view [epgItems]="epgItems" />
</div>
@if (player === 'videojs' || player === 'html5') { @if (streamUrl) {
<div class="video-player">
<app-web-player-view [streamUrl]="streamUrl" />
</div>
} } @if (streamUrl) {
<div class="epg">
<div class="epg-content">
<app-epg-view [epgItems]="epgItems" />
</div>
</div>
} @else {
<div class="no-channel-selected">Please select a channel</div>
<div class="no-channel-selected">Please select a channel</div>
}
</div>