mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
refactor(xtream): generalize xtream api related components
This commit is contained in:
1 parent
e23c8f7eef
commit
a302054af3
11 files changed
+123
-99
No files matched your search
@@ -19,7 +19,7 @@ import { XtreamStore } from '../xtream.store';
|
||||
MatTooltip,
|
||||
NgOptimizedImage,
|
||||
TranslateModule,
|
||||
]
|
||||
],
|
||||
})
|
||||
export class CategoryContentViewComponent {
|
||||
private readonly activatedRoute = inject(ActivatedRoute);
|
||||
@@ -39,7 +39,6 @@ export class CategoryContentViewComponent {
|
||||
}
|
||||
|
||||
onItemClick(item: any) {
|
||||
console.log(item);
|
||||
this.xtreamStore.setSelectedItem(item);
|
||||
this.router.navigate([item.xtream_id], {
|
||||
relativeTo: this.activatedRoute,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
@let items = xtreamStore.getCategoriesBySelectedType();
|
||||
@let contentType = xtreamStore.selectedContentType();
|
||||
@let selectedCategory = xtreamStore.selectedCategoryId();
|
||||
|
||||
@if (items?.length > 0) {
|
||||
@if (items()?.length > 0) {
|
||||
<mat-nav-list>
|
||||
@for (item of items; track $index) {
|
||||
@for (item of items(); track $index) {
|
||||
<mat-list-item
|
||||
class="category-item"
|
||||
[class.selected]="isSelected(item)"
|
||||
@@ -14,8 +10,7 @@
|
||||
item.category_name || $any(item).name || 'No category name'
|
||||
}}
|
||||
</mat-list-item>
|
||||
}
|
||||
@if (!items?.length) {
|
||||
} @empty {
|
||||
<app-playlist-error-view
|
||||
title="No results"
|
||||
[description]="
|
||||
|
||||
@@ -1,47 +1,30 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Output,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
} from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { XtreamCategory } from '../../../../shared/xtream-category.interface';
|
||||
import { PlaylistErrorViewComponent } from '../playlist-error-view/playlist-error-view.component';
|
||||
import { XtreamStore } from '../xtream.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-category-view',
|
||||
imports: [
|
||||
MatCardModule,
|
||||
MatListModule,
|
||||
MatTooltipModule,
|
||||
PlaylistErrorViewComponent,
|
||||
TranslateModule,
|
||||
],
|
||||
imports: [MatListModule, PlaylistErrorViewComponent, TranslatePipe],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './category-view.component.html',
|
||||
styleUrls: ['./category-view.component.scss']
|
||||
styleUrls: ['./category-view.component.scss'],
|
||||
})
|
||||
export class CategoryViewComponent {
|
||||
@Output() categoryClicked = new EventEmitter<XtreamCategory>();
|
||||
readonly items = input([]);
|
||||
readonly selectedCategoryId = input<number>();
|
||||
|
||||
readonly xtreamStore = inject(XtreamStore);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
ngOnInit(): void {
|
||||
const { categoryId } = this.route.snapshot.params;
|
||||
if (categoryId)
|
||||
this.xtreamStore.setSelectedCategory(Number(categoryId));
|
||||
}
|
||||
readonly categoryClicked = output<XtreamCategory>();
|
||||
|
||||
isSelected(item: XtreamCategory): boolean {
|
||||
const selectedCategory = this.xtreamStore.selectedCategoryId();
|
||||
const selectedCategory = this.selectedCategoryId();
|
||||
const itemId = Number((item as any).category_id || item.id);
|
||||
return selectedCategory !== null && selectedCategory === itemId;
|
||||
return selectedCategory !== null && Number(selectedCategory) === itemId;
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,18 @@
|
||||
<button mat-icon-button (click)="backToCategories()">
|
||||
<mat-icon>chevron_left</mat-icon>
|
||||
</button>
|
||||
<h2>Channels</h2>
|
||||
<h2>{{ 'HOME.PLAYLISTS.CHANNELS' | translate }}</h2>
|
||||
} @else {
|
||||
<h2>Categories</h2>
|
||||
<h2>{{ 'PORTALS.ALL_CATEGORIES' | translate }}</h2>
|
||||
}
|
||||
</div>
|
||||
@if (selectedCategoryId()) {
|
||||
<app-portal-channels-list (playClicked)="playLive($event)" />
|
||||
} @else {
|
||||
<app-category-view (categoryClicked)="selectCategory($event)" />
|
||||
<app-category-view
|
||||
[items]="categories()"
|
||||
(categoryClicked)="selectCategory($event)"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div class="content-container">
|
||||
@@ -32,6 +35,8 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="no-channel-selected">Select a category and channel</div>
|
||||
<div class="no-channel-selected">
|
||||
{{ 'PORTALS.SELECT_CATEGORY' | translate }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -11,7 +11,7 @@ import { MatIcon } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { XtreamCategory } from '../../../../shared/xtream-category.interface';
|
||||
import { EpgViewComponent } from '../../portals/epg-view/epg-view.component';
|
||||
import { WebPlayerViewComponent } from '../../portals/web-player-view/web-player-view.component';
|
||||
@@ -36,16 +36,18 @@ import { XtreamStore } from '../xtream.store';
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
PortalChannelsListComponent,
|
||||
TranslateModule,
|
||||
TranslatePipe,
|
||||
WebPlayerViewComponent,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class LiveStreamLayoutComponent implements OnInit {
|
||||
private favoritesService = inject(FavoritesService);
|
||||
private playerService = inject(PlayerService);
|
||||
private readonly xtreamStore = inject(XtreamStore);
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
|
||||
readonly categories = this.xtreamStore.getCategoriesBySelectedType;
|
||||
readonly epgItems = this.xtreamStore.epgItems;
|
||||
readonly selectedCategoryId = this.xtreamStore.selectedCategoryId;
|
||||
private readonly hideExternalInfoDialog =
|
||||
@@ -80,7 +82,6 @@ export class LiveStreamLayoutComponent implements OnInit {
|
||||
this.xtreamStore.currentPlaylist();
|
||||
const streamFormat = this.settingsStore.streamFormat();
|
||||
const streamUrl = `${serverUrl}/live/${username}/${password}/${item.xtream_id}.${streamFormat}`;
|
||||
// TODO: offer option to select TS or m3u8
|
||||
this.openPlayer(streamUrl, item.title, item.poster_url);
|
||||
this.xtreamStore.setSelectedItem(item);
|
||||
this.xtreamStore.loadEpg();
|
||||
@@ -99,7 +100,6 @@ export class LiveStreamLayoutComponent implements OnInit {
|
||||
|
||||
selectCategory(category: XtreamCategory) {
|
||||
const categoryId = (category as any).category_id ?? category.id;
|
||||
console.log('Selected category:', categoryId);
|
||||
this.xtreamStore.setSelectedCategory(categoryId);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
routerLink="/"
|
||||
routerLinkActive="active"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
matTooltip="Home"
|
||||
[matTooltip]="'PORTALS.SIDEBAR.HOME' | translate"
|
||||
>
|
||||
<mat-icon>home</mat-icon>
|
||||
</button>
|
||||
@@ -26,7 +26,7 @@
|
||||
mat-icon-button
|
||||
class="action-button"
|
||||
(click)="openAccountInfo()"
|
||||
matTooltip="Account Info"
|
||||
[matTooltip]="'PORTALS.SIDEBAR.ACCOUNT_INFO' | translate"
|
||||
>
|
||||
<mat-icon>person</mat-icon>
|
||||
</button>
|
||||
@@ -34,7 +34,7 @@
|
||||
mat-icon-button
|
||||
class="action-button"
|
||||
(click)="openPlaylistInfo()"
|
||||
matTooltip="Playlist Info"
|
||||
[matTooltip]="'PORTALS.SIDEBAR.PLAYLIST_INFO' | translate"
|
||||
>
|
||||
<mat-icon>playlist_play</mat-icon>
|
||||
</button>
|
||||
@@ -43,67 +43,75 @@
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<div class="nav-group-label">Main</div>
|
||||
<div class="nav-group-label">{{ 'PORTALS.SIDEBAR.MAIN' | translate }}</div>
|
||||
<mat-nav-list>
|
||||
<a
|
||||
mat-list-item
|
||||
(click)="xtreamStore.setSelectedContentType('vod')"
|
||||
routerLink="./vod"
|
||||
(click)="categoryClick.emit('vod')"
|
||||
[class.active]="isContentTypeActive('vod')"
|
||||
>
|
||||
<mat-icon matListItemIcon>movie</mat-icon>
|
||||
<span matListItemTitle>Movies</span>
|
||||
<span matListItemTitle>{{
|
||||
'PORTALS.SIDEBAR.MOVIES' | translate
|
||||
}}</span>
|
||||
</a>
|
||||
<a
|
||||
mat-list-item
|
||||
(click)="xtreamStore.setSelectedContentType('live')"
|
||||
routerLink="./live"
|
||||
(click)="categoryClick.emit('live')"
|
||||
[class.active]="isContentTypeActive('live')"
|
||||
>
|
||||
<mat-icon matListItemIcon>live_tv</mat-icon>
|
||||
<span matListItemTitle>Live TV</span>
|
||||
<span matListItemTitle>{{
|
||||
'PORTALS.SIDEBAR.LIVE_TV' | translate
|
||||
}}</span>
|
||||
</a>
|
||||
<a
|
||||
mat-list-item
|
||||
routerLink="./series"
|
||||
(click)="xtreamStore.setSelectedContentType('series')"
|
||||
(click)="categoryClick.emit('series')"
|
||||
[class.active]="isContentTypeActive('series')"
|
||||
>
|
||||
<mat-icon matListItemIcon>tv</mat-icon>
|
||||
<span matListItemTitle>TV Series</span>
|
||||
<span matListItemTitle>{{
|
||||
'PORTALS.SIDEBAR.SERIES' | translate
|
||||
}}</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<div class="nav-group-label">Library</div>
|
||||
<div class="nav-group-label">
|
||||
{{ 'PORTALS.SIDEBAR.LIBRARY' | translate }}
|
||||
</div>
|
||||
<mat-nav-list>
|
||||
<a
|
||||
mat-list-item
|
||||
routerLink="./search"
|
||||
routerLinkActive="active"
|
||||
(click)="xtreamStore.setSelectedContentType(undefined)"
|
||||
(click)="pageClicked.emit('search')"
|
||||
>
|
||||
<mat-icon matListItemIcon>search</mat-icon>
|
||||
<span matListItemTitle>Search</span>
|
||||
<span matListItemTitle>{{
|
||||
'PORTALS.SIDEBAR.SEARCH' | translate
|
||||
}}</span>
|
||||
</a>
|
||||
<a
|
||||
mat-list-item
|
||||
routerLink="./recent"
|
||||
routerLinkActive="active"
|
||||
(click)="xtreamStore.setSelectedContentType(undefined)"
|
||||
(click)="pageClicked.emit('recent')"
|
||||
>
|
||||
<mat-icon matListItemIcon>history</mat-icon>
|
||||
<span matListItemTitle>Recent</span>
|
||||
<span matListItemTitle>{{
|
||||
'PORTALS.SIDEBAR.RECENT' | translate
|
||||
}}</span>
|
||||
</a>
|
||||
<a
|
||||
mat-list-item
|
||||
routerLink="./favorites"
|
||||
routerLinkActive="active"
|
||||
(click)="xtreamStore.setSelectedContentType(undefined)"
|
||||
(click)="pageClicked.emit('favorites')"
|
||||
>
|
||||
<mat-icon matListItemIcon>favorite</mat-icon>
|
||||
<span matListItemTitle>Favorites</span>
|
||||
<span matListItemTitle>{{
|
||||
'PORTALS.SIDEBAR.FAVORITES' | translate
|
||||
}}</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, inject, input, output } from '@angular/core';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { PlaylistInfoComponent } from '../../home/recent-playlists/playlist-info/playlist-info.component';
|
||||
import { SettingsComponent } from '../../settings/settings.component';
|
||||
import { selectActivePlaylist } from '../../state/selectors';
|
||||
import { selectPlaylistById } from '../../state/selectors';
|
||||
import { AccountInfoComponent } from '../account-info/account-info.component';
|
||||
import { XtreamStore } from '../xtream.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation',
|
||||
imports: [
|
||||
MatListModule,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatListModule,
|
||||
MatTooltip,
|
||||
RouterLink,
|
||||
RouterLinkActive,
|
||||
TranslateModule,
|
||||
MatTooltipModule,
|
||||
TranslatePipe,
|
||||
],
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrl: './navigation.component.scss'
|
||||
styleUrl: './navigation.component.scss',
|
||||
})
|
||||
export class NavigationComponent {
|
||||
private readonly activatedRoute = inject(ActivatedRoute);
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly store = inject(Store);
|
||||
readonly xtreamStore = inject(XtreamStore);
|
||||
|
||||
// Make currentPlaylist public so template can access it
|
||||
readonly currentPlaylist = this.store.selectSignal(selectActivePlaylist);
|
||||
readonly portalStatus = input<'active' | 'inactive' | 'expired'>();
|
||||
|
||||
ngOnInit() {
|
||||
this.xtreamStore.checkPortalStatus();
|
||||
}
|
||||
readonly categoryClick = output<'vod' | 'live' | 'series'>();
|
||||
readonly pageClicked = output<'search' | 'recent' | 'favorites'>();
|
||||
|
||||
readonly currentPlaylist = this.store.selectSignal(
|
||||
selectPlaylistById(this.activatedRoute.snapshot.params.id)
|
||||
);
|
||||
|
||||
getStatusColor(): string {
|
||||
const status = this.xtreamStore.portalStatus();
|
||||
switch (status) {
|
||||
switch (this.portalStatus()) {
|
||||
case 'active':
|
||||
return 'status-active';
|
||||
case 'inactive':
|
||||
@@ -54,8 +56,7 @@ export class NavigationComponent {
|
||||
}
|
||||
|
||||
getStatusIcon(): string {
|
||||
const status = this.xtreamStore.portalStatus();
|
||||
switch (status) {
|
||||
switch (this.portalStatus()) {
|
||||
case 'active':
|
||||
return 'check_circle';
|
||||
case 'inactive':
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>Categories</h2>
|
||||
<h2>{{ 'PORTALS.ALL_CATEGORIES' | translate }}</h2>
|
||||
</div>
|
||||
<app-category-view (categoryClicked)="categoryClicked($event)" />
|
||||
<app-category-view
|
||||
[items]="categories()"
|
||||
[selectedCategoryId]="selectedCategoryId()"
|
||||
(categoryClicked)="categoryClicked($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { CategoryViewComponent } from './category-view/category-view.component';
|
||||
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
@@ -20,15 +20,22 @@ import { XtreamStore } from './xtream.store';
|
||||
MpvPlayerBarComponent,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
]
|
||||
],
|
||||
})
|
||||
export class XtreamMainContainerComponent {
|
||||
export class XtreamMainContainerComponent implements OnInit {
|
||||
readonly router = inject(Router);
|
||||
readonly route = inject(ActivatedRoute);
|
||||
readonly xtreamStore = inject(XtreamStore);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
readonly categories = this.xtreamStore.getCategoriesBySelectedType;
|
||||
|
||||
readonly selectedCategoryId = this.xtreamStore.selectedCategoryId;
|
||||
|
||||
ngOnInit(): void {
|
||||
const { categoryId } = this.route.snapshot.params;
|
||||
if (categoryId)
|
||||
this.xtreamStore.setSelectedCategory(Number(categoryId));
|
||||
}
|
||||
|
||||
categoryClicked(category: XtreamCategory) {
|
||||
const categoryId = (category as any).category_id ?? category.id;
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
}
|
||||
|
||||
<div class="xtream-container">
|
||||
<app-navigation class="navigation-sidebar" />
|
||||
<app-navigation
|
||||
class="navigation-sidebar"
|
||||
[portalStatus]="portalStatus()"
|
||||
(categoryClick)="handleCategoryClick($event)"
|
||||
(pageClicked)="handlePageClick($event)"
|
||||
/>
|
||||
<router-outlet />
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, effect, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterOutlet } from '@angular/router';
|
||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import * as PlaylistActions from '../../state/actions';
|
||||
@@ -16,16 +16,18 @@ import { XtreamStore } from '../xtream.store';
|
||||
RouterOutlet,
|
||||
TranslateModule,
|
||||
],
|
||||
providers: [XtreamStore]
|
||||
providers: [XtreamStore],
|
||||
})
|
||||
export class XtreamShellComponent {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
private readonly store = inject(Store);
|
||||
private readonly xtreamStore = inject(XtreamStore);
|
||||
|
||||
readonly getImportCount = this.xtreamStore.getImportCount;
|
||||
readonly isImporting = this.xtreamStore.isImporting;
|
||||
readonly itemsToImport = this.xtreamStore.itemsToImport;
|
||||
readonly portalStatus = this.xtreamStore.portalStatus;
|
||||
|
||||
constructor() {
|
||||
effect(
|
||||
@@ -39,10 +41,25 @@ export class XtreamShellComponent {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.xtreamStore.checkPortalStatus();
|
||||
this.store.dispatch(
|
||||
PlaylistActions.setActivePlaylist({
|
||||
playlistId: this.route.snapshot.params.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
handleCategoryClick(category: 'vod' | 'live' | 'series') {
|
||||
this.xtreamStore.setSelectedContentType(category);
|
||||
this.router.navigate([category], {
|
||||
relativeTo: this.route,
|
||||
});
|
||||
}
|
||||
|
||||
handlePageClick(page: 'search' | 'recent' | 'favorites') {
|
||||
this.xtreamStore.setSelectedContentType(undefined);
|
||||
this.router.navigate([page], {
|
||||
relativeTo: this.route,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user