mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat: add sorting button for VOD and series
This commit is contained in:
1 parent
35d83149eb
commit
81cbc1e5d6
10 files changed
+111
-15
No files matched your search
@@ -0,0 +1,27 @@
|
||||
import { Injectable, Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'sortBy',
|
||||
standalone: true,
|
||||
})
|
||||
@Injectable()
|
||||
export class SortPipe implements PipeTransform {
|
||||
transform(array: any[], sortType: string): any {
|
||||
if (!array || !sortType) {
|
||||
return array;
|
||||
}
|
||||
return array.sort((a, b) => {
|
||||
if (sortType === 'date') {
|
||||
return parseInt(b.added) - parseInt(a.added);
|
||||
} else if (sortType === 'rating') {
|
||||
const ratingA = isNaN(parseFloat(a.rating)) ? 0 : parseFloat(a.rating);
|
||||
const ratingB = isNaN(parseFloat(b.rating)) ? 0 : parseFloat(b.rating);
|
||||
return ratingB - ratingA;
|
||||
} else if (sortType === 'alpha') {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@if (items?.length > 0) {
|
||||
<div class="grid">
|
||||
@for (item of items | filterBy: searchPhrase() : 'name'; track $index) {
|
||||
@for (item of items | filterBy: searchPhrase() : 'name' | sortBy: sortType(); track $index) {
|
||||
<mat-card (click)="itemClicked.emit(item)">
|
||||
<mat-card-content>
|
||||
@if (item.stream_icon || item.cover || item.logo) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { XtreamItem } from '../../../../shared/xtream-item.interface';
|
||||
import { FilterPipe } from '../../shared/pipes/filter.pipe';
|
||||
import { SortPipe } from '../../shared/pipes/sort.pipe';
|
||||
import { PlaylistErrorViewComponent } from '../playlist-error-view/playlist-error-view.component';
|
||||
import { PortalStore } from '../portal.store';
|
||||
|
||||
@@ -16,6 +17,7 @@ import { PortalStore } from '../portal.store';
|
||||
standalone: true,
|
||||
imports: [
|
||||
FilterPipe,
|
||||
SortPipe,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
@@ -30,4 +32,5 @@ export class CategoryContentViewComponent {
|
||||
|
||||
portalStore = inject(PortalStore);
|
||||
searchPhrase = this.portalStore.searchPhrase;
|
||||
sortType = this.portalStore.sortType;
|
||||
}
|
||||
@@ -50,16 +50,46 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search" *ngIf="searchVisible">
|
||||
<mat-form-field class="search-input">
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
placeholder="Search by name"
|
||||
type="search"
|
||||
[ngModel]="searchPhrase()"
|
||||
(ngModelChange)="searchPhraseUpdate.next($event)"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="search-container">
|
||||
<div class="sort" *ngIf="sortVisible">
|
||||
<button
|
||||
mat-icon-button
|
||||
[matTooltip]="'CHANNELS.SORT_BY' | translate"
|
||||
[matMenuTriggerFor]="sortPlaylistMenu"
|
||||
data-test-id="sort-playlist"
|
||||
>
|
||||
<mat-icon>sort</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="search" *ngIf="searchVisible">
|
||||
<mat-form-field class="search-input">
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
placeholder="Search by name"
|
||||
type="search"
|
||||
[ngModel]="searchPhrase()"
|
||||
(ngModelChange)="searchPhraseUpdate.next($event)"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<mat-menu #sortPlaylistMenu="matMenu">
|
||||
<section
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<button mat-menu-item (click)="setSortType('date')">
|
||||
<mat-icon>calendar_today</mat-icon>
|
||||
<span>{{ 'CHANNELS.SORT_BY_DATE' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setSortType('rating')">
|
||||
<mat-icon>star</mat-icon>
|
||||
<span>{{ 'CHANNELS.SORT_BY_RATING' | translate }}</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="setSortType('alpha')">
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
<span>{{ 'CHANNELS.SORT_BY_NAME' | translate }}</span>
|
||||
</button>
|
||||
</section>
|
||||
</mat-menu>
|
||||
</div>
|
||||
@@ -40,6 +40,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ import { Breadcrumb } from '../breadcrumb.interface';
|
||||
import { ContentTypeNavigationItem } from '../content-type-navigation-item.interface';
|
||||
import { ContentType } from '../content-type.enum';
|
||||
import { PortalStore } from '../portal.store';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation-bar',
|
||||
@@ -34,12 +37,16 @@ import { PortalStore } from '../portal.store';
|
||||
NgIf,
|
||||
RouterLink,
|
||||
TranslateModule,
|
||||
MatMenuModule,
|
||||
MatCheckboxModule,
|
||||
MatTooltipModule
|
||||
],
|
||||
})
|
||||
export class NavigationBarComponent {
|
||||
@Input({ required: true }) breadcrumbs: Breadcrumb[];
|
||||
@Input({ required: true }) contentType: ContentType;
|
||||
@Input() searchVisible = true;
|
||||
@Input() sortVisible = false;
|
||||
@Input() contentTypeNavigationItems: ContentTypeNavigationItem[];
|
||||
@Input() clientSideSearch = true;
|
||||
@Input() showCategories = false;
|
||||
@@ -56,6 +63,7 @@ export class NavigationBarComponent {
|
||||
searchPhrase = this.portalStore.searchPhrase;
|
||||
searchPhraseUpdate = new Subject<string>();
|
||||
currentPlaylist = this.store.selectSignal(selectCurrentPlaylist);
|
||||
sortType = this.portalStore.sortType;
|
||||
|
||||
constructor() {
|
||||
this.searchPhraseUpdate
|
||||
@@ -76,6 +84,10 @@ export class NavigationBarComponent {
|
||||
this.breadcrumbClicked.emit(item);
|
||||
}
|
||||
|
||||
setSortType(type: string) {
|
||||
this.portalStore.setSortType(type);
|
||||
}
|
||||
|
||||
setSearchText(text: string) {
|
||||
if (this.clientSideSearch) this.portalStore.setSearchPhrase(text);
|
||||
else this.searchTextChanged.emit(text);
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface PortalState {
|
||||
hideExternalInfoDialog: boolean;
|
||||
currentVod: any;
|
||||
currentSerial: any;
|
||||
sortType: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -19,6 +20,7 @@ export class PortalStore extends ComponentStore<PortalState> {
|
||||
content: [],
|
||||
hideExternalInfoDialog:
|
||||
localStorage.getItem('hideExternalInfoDialog') === 'true',
|
||||
sortType: undefined
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,11 +28,13 @@ export class PortalStore extends ComponentStore<PortalState> {
|
||||
readonly searchPhrase = this.selectSignal((state) => state.searchPhrase);
|
||||
readonly currentSerial = this.selectSignal((state) => state.currentSerial);
|
||||
readonly currentVod = this.selectSignal((state) => state.currentVod);
|
||||
|
||||
|
||||
readonly hideExternalInfoDialog = this.selectSignal(
|
||||
(state) => state.hideExternalInfoDialog
|
||||
);
|
||||
|
||||
readonly sortType = this.selectSignal((state) => state.sortType);
|
||||
|
||||
readonly getContentById = (id: string) =>
|
||||
this.selectSignal((state) => state.content.find((i) => i.id === id));
|
||||
|
||||
@@ -72,4 +76,11 @@ export class PortalStore extends ComponentStore<PortalState> {
|
||||
return { ...state, hideExternalInfoDialog };
|
||||
}
|
||||
);
|
||||
|
||||
readonly setSortType = this.updater(
|
||||
(state, sortType: string): PortalState => ({
|
||||
...state,
|
||||
sortType,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
[searchVisible]="
|
||||
currentLayout === 'category' || currentLayout === 'category_content'
|
||||
"
|
||||
[sortVisible]="currentLayout === 'category_content'"
|
||||
[contentTypeNavigationItems]="navigationContentTypes"
|
||||
/>
|
||||
|
||||
|
||||
@@ -155,7 +155,11 @@
|
||||
"NO_FAVORITES": "No favorites yet",
|
||||
"USE_STAR_TO_FAVORITE": "Please use 'star' icon to add channels to your favorites.",
|
||||
"REMOVE_FAVORITE": "Remove from favorites",
|
||||
"FAVORITES_UPDATED": "Favorites were updated!"
|
||||
"FAVORITES_UPDATED": "Favorites were updated!",
|
||||
"SORT_BY": "Sort by",
|
||||
"SORT_BY_DATE": "Sort by Date Added (Newest First)",
|
||||
"SORT_BY_RATING": "Sort by Rating",
|
||||
"SORT_BY_NAME": "Sort by Name (A-Z)"
|
||||
},
|
||||
"TOP_MENU": {
|
||||
"OPEN_CHANNELS_LIST": "Open channel list",
|
||||
|
||||
@@ -155,7 +155,11 @@
|
||||
"NO_FAVORITES": "Henüz favori yok",
|
||||
"USE_STAR_TO_FAVORITE": "Kanalları favorilerinize eklemek için lütfen 'yıldız' simgesini kullanın.",
|
||||
"REMOVE_FAVORITE": "Favorilerden Kaldır",
|
||||
"FAVORITES_UPDATED": "Favoriler güncellendi!"
|
||||
"FAVORITES_UPDATED": "Favoriler güncellendi!",
|
||||
"SORT_BY": "Sırala",
|
||||
"SORT_BY_DATE": "Eklenme Tarihine Göre Sırala (Önce En Yeni)",
|
||||
"SORT_BY_RATING": "Puana Göre Sırala",
|
||||
"SORT_BY_NAME": "İsmine Göre Sırala (A-Z)"
|
||||
},
|
||||
"TOP_MENU": {
|
||||
"OPEN_CHANNELS_LIST": "Kanal listesini aç",
|
||||
|
||||
Reference in new issue
Block a user