mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
refactor(favorites): restructure favorites component for improved category handling and UI layout
This commit is contained in:
1 parent
818e729c75
commit
a7f4e97f6f
3 files changed
+143
-71
No files matched your search
@@ -1,41 +1,37 @@
|
|||||||
<div class="favorites-container">
|
<div class="sidebar">
|
||||||
<div class="header">
|
<div class="sidebar-header">
|
||||||
<h2>{{ 'CHANNELS.FAVORITES' | translate }}</h2>
|
<h2>{{ 'PORTALS.ALL_CATEGORIES' | translate }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<app-category-view
|
||||||
|
[items]="categories()"
|
||||||
|
[selectedCategoryId]="selectedCategoryId()"
|
||||||
|
(categoryClicked)="setCategoryId($event.category_id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-tab-group>
|
<div class="main-content">
|
||||||
<mat-tab label="Live TV ({{ liveCount }})">
|
<div class="content-header">
|
||||||
<ng-container
|
<h2>
|
||||||
*ngTemplateOutlet="itemsGrid; context: { type: 'live' }"
|
<h2>
|
||||||
>
|
{{
|
||||||
</ng-container>
|
selectedCategoryId() +
|
||||||
</mat-tab>
|
' ' +
|
||||||
<mat-tab label="Movies ({{ moviesCount }})">
|
('CHANNELS.FAVORITES' | translate)
|
||||||
<ng-container
|
}}
|
||||||
*ngTemplateOutlet="itemsGrid; context: { type: 'movie' }"
|
</h2>
|
||||||
>
|
</h2>
|
||||||
</ng-container>
|
</div>
|
||||||
</mat-tab>
|
<div class="scrollable-content">
|
||||||
<mat-tab label="Series ({{ seriesCount }})">
|
@if (favoritesToShow().length) {
|
||||||
<ng-container
|
|
||||||
*ngTemplateOutlet="itemsGrid; context: { type: 'series' }"
|
|
||||||
>
|
|
||||||
</ng-container>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
|
|
||||||
<ng-template #itemsGrid let-type="type">
|
|
||||||
@if ((favorites$ | async | filterByType: type).length) {
|
|
||||||
<div class="items-grid">
|
<div class="items-grid">
|
||||||
@for (
|
@for (item of favoritesToShow(); track item.content_id) {
|
||||||
item of favorites$ | async | filterByType: type;
|
|
||||||
track $any(item).id
|
|
||||||
) {
|
|
||||||
<mat-card class="item-card" (click)="openItem(item)">
|
<mat-card class="item-card" (click)="openItem(item)">
|
||||||
<img
|
<img
|
||||||
[src]="
|
[src]="
|
||||||
item.poster_url ||
|
item.poster_url ||
|
||||||
'assets/default-' + type + '.png'
|
'assets/default-' +
|
||||||
|
selectedCategoryId() +
|
||||||
|
'.png'
|
||||||
"
|
"
|
||||||
(error)="
|
(error)="
|
||||||
$event.target.src =
|
$event.target.src =
|
||||||
@@ -68,8 +64,9 @@
|
|||||||
} @else {
|
} @else {
|
||||||
<div class="no-items">
|
<div class="no-items">
|
||||||
<mat-icon>favorite_border</mat-icon>
|
<mat-icon>favorite_border</mat-icon>
|
||||||
<p>No favorite {{ type }} items</p>
|
<p>No favorite {{ selectedCategoryId() }} items</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ng-template>
|
</div>
|
||||||
|
<app-mpv-player-bar />
|
||||||
</div>
|
</div>
|
||||||
@@ -1,9 +1,36 @@
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row; // Changed from column to row
|
||||||
overflow-y: auto;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%; // Added to ensure full width
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0; // Important for proper flex behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header {
|
||||||
|
text-transform: capitalize;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.favorites-container {
|
.favorites-container {
|
||||||
@@ -96,4 +123,4 @@ mat-card-content {
|
|||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import { AsyncPipe, DatePipe, NgTemplateOutlet } from '@angular/common';
|
import { DatePipe } from '@angular/common';
|
||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject, signal } from '@angular/core';
|
||||||
import { MatIconButton } from '@angular/material/button';
|
import { MatIconButton } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslatePipe } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { BehaviorSubject, switchMap } from 'rxjs';
|
||||||
import { FilterByTypePipe } from '../../shared/pipes/filter-by-type.pipe';
|
import { XtreamCategory } from '../../../../shared/xtream-category.interface';
|
||||||
|
import { MpvPlayerBarComponent } from '../../shared/components/mpv-player-bar/mpv-player-bar.component';
|
||||||
import { selectActivePlaylist } from '../../state/selectors';
|
import { selectActivePlaylist } from '../../state/selectors';
|
||||||
|
import { CategoryViewComponent } from '../category-view/category-view.component';
|
||||||
import { FavoriteItem } from '../services/favorite-item.interface';
|
import { FavoriteItem } from '../services/favorite-item.interface';
|
||||||
import { FavoritesService } from '../services/favorites.service';
|
import { FavoritesService } from '../services/favorites.service';
|
||||||
import { XtreamStore } from '../xtream.store';
|
import { XtreamStore } from '../xtream.store';
|
||||||
@@ -17,18 +18,16 @@ import { XtreamStore } from '../xtream.store';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-favorites',
|
selector: 'app-favorites',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
CategoryViewComponent,
|
||||||
DatePipe,
|
DatePipe,
|
||||||
FilterByTypePipe,
|
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
MatIcon,
|
MatIcon,
|
||||||
MatIconButton,
|
MatIconButton,
|
||||||
MatTabsModule,
|
MpvPlayerBarComponent,
|
||||||
NgTemplateOutlet,
|
TranslatePipe,
|
||||||
TranslateModule,
|
|
||||||
],
|
],
|
||||||
templateUrl: './favorites.component.html',
|
templateUrl: './favorites.component.html',
|
||||||
styleUrl: './favorites.component.scss'
|
styleUrls: ['./favorites.component.scss', '../sidebar.scss'],
|
||||||
})
|
})
|
||||||
export class FavoritesComponent implements OnInit {
|
export class FavoritesComponent implements OnInit {
|
||||||
private favoritesService = inject(FavoritesService);
|
private favoritesService = inject(FavoritesService);
|
||||||
@@ -37,27 +36,78 @@ export class FavoritesComponent implements OnInit {
|
|||||||
private store = inject(Store);
|
private store = inject(Store);
|
||||||
private xtreamStore = inject(XtreamStore);
|
private xtreamStore = inject(XtreamStore);
|
||||||
|
|
||||||
favorites$: Observable<FavoriteItem[]>;
|
readonly categories = signal<XtreamCategory[]>([]);
|
||||||
liveCount = 0;
|
|
||||||
moviesCount = 0;
|
readonly series = signal<FavoriteItem[]>([]);
|
||||||
seriesCount = 0;
|
readonly movies = signal<FavoriteItem[]>([]);
|
||||||
|
readonly live = signal<FavoriteItem[]>([]);
|
||||||
|
|
||||||
|
readonly favoritesToShow = signal<FavoriteItem[]>([]);
|
||||||
|
readonly selectedCategoryId = signal<string>('movie');
|
||||||
|
|
||||||
|
private favoritesRefresh$ = new BehaviorSubject<void>(undefined);
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.xtreamStore.setSelectedContentType(undefined);
|
||||||
const playlistId = this.store.selectSignal(selectActivePlaylist)()._id;
|
const playlistId = this.store.selectSignal(selectActivePlaylist)()._id;
|
||||||
this.favorites$ = this.favoritesService.getFavorites(playlistId);
|
this.favoritesRefresh$
|
||||||
|
.pipe(
|
||||||
|
switchMap(() => this.favoritesService.getFavorites(playlistId))
|
||||||
|
)
|
||||||
|
.subscribe((items) => {
|
||||||
|
this.movies.set(
|
||||||
|
items.filter((item) => (item as any).type === 'movie')
|
||||||
|
);
|
||||||
|
this.live.set(items.filter((item) => item.type === 'live'));
|
||||||
|
this.series.set(items.filter((item) => item.type === 'series'));
|
||||||
|
this.initCategories();
|
||||||
|
this.setCategoryContent(this.selectedCategoryId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe to update counts
|
initCategories() {
|
||||||
this.favorites$.subscribe((items) => {
|
this.categories.set([
|
||||||
this.liveCount = items.filter(
|
{
|
||||||
(item) => item.type === 'live'
|
id: 1,
|
||||||
).length;
|
category_id: 'movie',
|
||||||
this.moviesCount = items.filter(
|
category_name: 'Movies' + ' (' + this.movies().length + ')',
|
||||||
(item) => (item as any).type === 'movie'
|
parent_id: 0,
|
||||||
).length;
|
},
|
||||||
this.seriesCount = items.filter(
|
{
|
||||||
(item) => item.type === 'series'
|
id: 2,
|
||||||
).length;
|
category_id: 'live',
|
||||||
});
|
category_name: 'Live TV' + ' (' + this.live().length + ')',
|
||||||
|
parent_id: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
category_id: 'series',
|
||||||
|
category_name: 'Series' + ' (' + this.series().length + ')',
|
||||||
|
parent_id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCategoryId(categoryId: string) {
|
||||||
|
this.selectedCategoryId.set(categoryId);
|
||||||
|
this.setCategoryContent(categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCategoryContent(categoryId: string) {
|
||||||
|
switch (categoryId) {
|
||||||
|
case 'movie':
|
||||||
|
this.favoritesToShow.set(this.movies());
|
||||||
|
break;
|
||||||
|
case 'live':
|
||||||
|
this.favoritesToShow.set(this.live());
|
||||||
|
break;
|
||||||
|
case 'series':
|
||||||
|
this.favoritesToShow.set(this.series());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.favoritesToShow.set(this.movies());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeFromFavorites(item: any) {
|
async removeFromFavorites(item: any) {
|
||||||
@@ -66,17 +116,15 @@ export class FavoritesComponent implements OnInit {
|
|||||||
item.playlist_id
|
item.playlist_id
|
||||||
);
|
);
|
||||||
// Refresh favorites after removal
|
// Refresh favorites after removal
|
||||||
const playlistId = this.store.selectSignal(selectActivePlaylist)()._id;
|
this.favoritesRefresh$.next();
|
||||||
this.favorites$ = this.favoritesService.getFavorites(playlistId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openItem(item: any) {
|
openItem(item: any) {
|
||||||
const type = item.type === 'movie' ? 'vod' : item.type;
|
const type = item.type === 'movie' ? 'vod' : item.type;
|
||||||
this.xtreamStore.setSelectedContentType(type);
|
this.xtreamStore.setSelectedContentType(type);
|
||||||
if (type === 'live') {
|
if (type === 'live') {
|
||||||
this.router.navigate(['..', type, item.category_id], {
|
const streamUrl = this.xtreamStore.constructStreamUrl(item);
|
||||||
relativeTo: this.route,
|
this.xtreamStore.openPlayer(streamUrl, item.title, item.poster_url);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(
|
this.router.navigate(
|
||||||
['..', type, item.category_id, item.xtream_id],
|
['..', type, item.category_id, item.xtream_id],
|
||||||
|
|||||||
Reference in new issue
Block a user