refactor(favorites): restructure favorites component for improved category handling and UI layout

This commit is contained in:
4gray committed 2025-05-04 23:06:45 +02:00
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="header">
<h2>{{ 'CHANNELS.FAVORITES' | translate }}</h2>
<div class="sidebar">
<div class="sidebar-header">
<h2>{{ 'PORTALS.ALL_CATEGORIES' | translate }}</h2>
</div>
<app-category-view
[items]="categories()"
[selectedCategoryId]="selectedCategoryId()"
(categoryClicked)="setCategoryId($event.category_id)"
/>
</div>
<mat-tab-group>
<mat-tab label="Live TV ({{ liveCount }})">
<ng-container
*ngTemplateOutlet="itemsGrid; context: { type: 'live' }"
>
</ng-container>
</mat-tab>
<mat-tab label="Movies ({{ moviesCount }})">
<ng-container
*ngTemplateOutlet="itemsGrid; context: { type: 'movie' }"
>
</ng-container>
</mat-tab>
<mat-tab label="Series ({{ seriesCount }})">
<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="main-content">
<div class="content-header">
<h2>
<h2>
{{
selectedCategoryId() +
' ' +
('CHANNELS.FAVORITES' | translate)
}}
</h2>
</h2>
</div>
<div class="scrollable-content">
@if (favoritesToShow().length) {
<div class="items-grid">
@for (
item of favorites$ | async | filterByType: type;
track $any(item).id
) {
@for (item of favoritesToShow(); track item.content_id) {
<mat-card class="item-card" (click)="openItem(item)">
<img
[src]="
item.poster_url ||
'assets/default-' + type + '.png'
'assets/default-' +
selectedCategoryId() +
'.png'
"
(error)="
$event.target.src =
@@ -68,8 +64,9 @@
} @else {
<div class="no-items">
<mat-icon>favorite_border</mat-icon>
<p>No favorite {{ type }} items</p>
<p>No favorite {{ selectedCategoryId() }} items</p>
</div>
}
</ng-template>
</div>
<app-mpv-player-bar />
</div>
@@ -1,9 +1,36 @@
:host {
display: flex;
flex-direction: column;
overflow-y: auto;
flex-direction: row; // Changed from column to row
overflow: hidden;
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 {
@@ -96,4 +123,4 @@ mat-card-content {
font-size: 1.1rem;
margin: 0;
}
}
}
@@ -1,15 +1,16 @@
import { AsyncPipe, DatePipe, NgTemplateOutlet } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { DatePipe } from '@angular/common';
import { Component, OnInit, inject, signal } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import { ActivatedRoute, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { FilterByTypePipe } from '../../shared/pipes/filter-by-type.pipe';
import { TranslatePipe } from '@ngx-translate/core';
import { BehaviorSubject, switchMap } from 'rxjs';
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 { CategoryViewComponent } from '../category-view/category-view.component';
import { FavoriteItem } from '../services/favorite-item.interface';
import { FavoritesService } from '../services/favorites.service';
import { XtreamStore } from '../xtream.store';
@@ -17,18 +18,16 @@ import { XtreamStore } from '../xtream.store';
@Component({
selector: 'app-favorites',
imports: [
AsyncPipe,
CategoryViewComponent,
DatePipe,
FilterByTypePipe,
MatCardModule,
MatIcon,
MatIconButton,
MatTabsModule,
NgTemplateOutlet,
TranslateModule,
MpvPlayerBarComponent,
TranslatePipe,
],
templateUrl: './favorites.component.html',
styleUrl: './favorites.component.scss'
styleUrls: ['./favorites.component.scss', '../sidebar.scss'],
})
export class FavoritesComponent implements OnInit {
private favoritesService = inject(FavoritesService);
@@ -37,27 +36,78 @@ export class FavoritesComponent implements OnInit {
private store = inject(Store);
private xtreamStore = inject(XtreamStore);
favorites$: Observable<FavoriteItem[]>;
liveCount = 0;
moviesCount = 0;
seriesCount = 0;
readonly categories = signal<XtreamCategory[]>([]);
readonly series = signal<FavoriteItem[]>([]);
readonly movies = signal<FavoriteItem[]>([]);
readonly live = signal<FavoriteItem[]>([]);
readonly favoritesToShow = signal<FavoriteItem[]>([]);
readonly selectedCategoryId = signal<string>('movie');
private favoritesRefresh$ = new BehaviorSubject<void>(undefined);
ngOnInit() {
this.xtreamStore.setSelectedContentType(undefined);
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
this.favorites$.subscribe((items) => {
this.liveCount = items.filter(
(item) => item.type === 'live'
).length;
this.moviesCount = items.filter(
(item) => (item as any).type === 'movie'
).length;
this.seriesCount = items.filter(
(item) => item.type === 'series'
).length;
});
initCategories() {
this.categories.set([
{
id: 1,
category_id: 'movie',
category_name: 'Movies' + ' (' + this.movies().length + ')',
parent_id: 0,
},
{
id: 2,
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) {
@@ -66,17 +116,15 @@ export class FavoritesComponent implements OnInit {
item.playlist_id
);
// Refresh favorites after removal
const playlistId = this.store.selectSignal(selectActivePlaylist)()._id;
this.favorites$ = this.favoritesService.getFavorites(playlistId);
this.favoritesRefresh$.next();
}
openItem(item: any) {
const type = item.type === 'movie' ? 'vod' : item.type;
this.xtreamStore.setSelectedContentType(type);
if (type === 'live') {
this.router.navigate(['..', type, item.category_id], {
relativeTo: this.route,
});
const streamUrl = this.xtreamStore.constructStreamUrl(item);
this.xtreamStore.openPlayer(streamUrl, item.title, item.poster_url);
} else {
this.router.navigate(
['..', type, item.category_id, item.xtream_id],