mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 13:46:16 -08:00
feat(xtream): add live tv channel sorting functionality
This commit is contained in:
1 parent
ba3247b782
commit
e3bc450803
5 files changed
+82
-4
No files matched your search
@@ -14,7 +14,37 @@
|
||||
<button mat-icon-button (click)="backToCategories()">
|
||||
<mat-icon>chevron_left</mat-icon>
|
||||
</button>
|
||||
<h2>{{ selectedCategoryInfo()?.name }} ({{ selectedCategoryInfo()?.count }})</h2>
|
||||
<h2 class="category-title">
|
||||
{{ selectedCategoryInfo()?.name }} ({{ selectedCategoryInfo()?.count }})
|
||||
</h2>
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="channelSortMenu"
|
||||
[matTooltip]="'Sort: ' + liveChannelSortLabel()"
|
||||
aria-label="Sort channels"
|
||||
>
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
</button>
|
||||
<mat-menu #channelSortMenu="matMenu">
|
||||
<button mat-menu-item (click)="setLiveChannelSortMode('server')">
|
||||
<span>Server Order</span>
|
||||
@if (liveChannelSortMode() === 'server') {
|
||||
<mat-icon>check</mat-icon>
|
||||
}
|
||||
</button>
|
||||
<button mat-menu-item (click)="setLiveChannelSortMode('name-asc')">
|
||||
<span>Name A-Z</span>
|
||||
@if (liveChannelSortMode() === 'name-asc') {
|
||||
<mat-icon>check</mat-icon>
|
||||
}
|
||||
</button>
|
||||
<button mat-menu-item (click)="setLiveChannelSortMode('name-desc')">
|
||||
<span>Name Z-A</span>
|
||||
@if (liveChannelSortMode() === 'name-desc') {
|
||||
<mat-icon>check</mat-icon>
|
||||
}
|
||||
</button>
|
||||
</mat-menu>
|
||||
} @else {
|
||||
<app-playlist-switcher
|
||||
class="switcher"
|
||||
@@ -32,7 +62,10 @@
|
||||
}
|
||||
</div>
|
||||
@if (selectedCategoryId()) {
|
||||
<app-portal-channels-list (playClicked)="playLive($event)" />
|
||||
<app-portal-channels-list
|
||||
[sortMode]="liveChannelSortMode()"
|
||||
(playClicked)="playLive($event)"
|
||||
/>
|
||||
} @else {
|
||||
<app-category-view
|
||||
[items]="categories()"
|
||||
|
||||
@@ -70,6 +70,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.category-title {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.no-channel-selected {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
inject,
|
||||
Injector,
|
||||
OnInit,
|
||||
signal,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
@@ -16,6 +17,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
@@ -34,6 +36,8 @@ import { PortalChannelsListComponent } from '../portal-channels-list/portal-chan
|
||||
import { FavoritesService } from '../services/favorites.service';
|
||||
import { XtreamStore } from '../stores/xtream.store';
|
||||
|
||||
type LiveChannelSortMode = 'server' | 'name-asc' | 'name-desc';
|
||||
|
||||
@Component({
|
||||
selector: 'app-live-stream-layout',
|
||||
templateUrl: './live-stream-layout.component.html',
|
||||
@@ -47,6 +51,7 @@ import { XtreamStore } from '../stores/xtream.store';
|
||||
MatIconButton,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
MatMenuModule,
|
||||
MatTooltipModule,
|
||||
/* MpvPlayerBarComponent, */
|
||||
PlaylistSwitcherComponent,
|
||||
@@ -69,6 +74,13 @@ export class LiveStreamLayoutComponent implements OnInit {
|
||||
readonly currentPlaylist = this.xtreamStore.currentPlaylist;
|
||||
readonly epgItems = this.xtreamStore.epgItems;
|
||||
readonly selectedCategoryId = this.xtreamStore.selectedCategoryId;
|
||||
readonly liveChannelSortMode = signal<LiveChannelSortMode>('server');
|
||||
readonly liveChannelSortLabel = computed(() => {
|
||||
const mode = this.liveChannelSortMode();
|
||||
if (mode === 'name-asc') return 'Name A-Z';
|
||||
if (mode === 'name-desc') return 'Name Z-A';
|
||||
return 'Server Order';
|
||||
});
|
||||
|
||||
readonly selectedCategoryInfo = computed(() => {
|
||||
const categoryId = this.selectedCategoryId();
|
||||
@@ -173,4 +185,8 @@ export class LiveStreamLayoutComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setLiveChannelSortMode(mode: LiveChannelSortMode): void {
|
||||
this.liveChannelSortMode.set(mode);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
>
|
||||
<div
|
||||
*cdkVirtualFor="
|
||||
let item of channels() | filterBy: searchString() : 'title';
|
||||
let item of sortedChannels() | filterBy: searchString() : 'title';
|
||||
trackBy: trackBy;
|
||||
templateCacheSize: 0
|
||||
"
|
||||
|
||||
+25
-1
@@ -7,7 +7,9 @@ import {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
input,
|
||||
OnDestroy,
|
||||
output,
|
||||
signal,
|
||||
@@ -37,6 +39,8 @@ interface EpgProgram {
|
||||
stop_timestamp: string;
|
||||
}
|
||||
|
||||
type LiveChannelSortMode = 'server' | 'name-asc' | 'name-desc';
|
||||
|
||||
@Component({
|
||||
selector: 'app-portal-channels-list',
|
||||
templateUrl: './portal-channels-list.component.html',
|
||||
@@ -54,12 +58,32 @@ interface EpgProgram {
|
||||
})
|
||||
export class PortalChannelsListComponent implements AfterViewInit, OnDestroy {
|
||||
readonly playClicked = output<any>();
|
||||
readonly sortMode = input<LiveChannelSortMode>('server');
|
||||
|
||||
readonly xtreamStore = inject(XtreamStore);
|
||||
private readonly favoritesService = inject(FavoritesService);
|
||||
private readonly epgQueueService = inject(EpgQueueService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
readonly channels = this.xtreamStore.selectItemsFromSelectedCategory;
|
||||
readonly sortedChannels = computed(() => {
|
||||
const mode = this.sortMode();
|
||||
const channels = this.channels();
|
||||
if (mode === 'server') {
|
||||
return channels;
|
||||
}
|
||||
|
||||
const collator = new Intl.Collator(undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'base',
|
||||
});
|
||||
|
||||
return [...channels].sort((a: any, b: any) => {
|
||||
const titleA = a.title ?? a.name ?? '';
|
||||
const titleB = b.title ?? b.name ?? '';
|
||||
const result = collator.compare(titleA, titleB);
|
||||
return mode === 'name-asc' ? result : -result;
|
||||
});
|
||||
});
|
||||
|
||||
favorites = new Map<number, boolean>();
|
||||
searchString = signal<string>('');
|
||||
@@ -117,7 +141,7 @@ export class PortalChannelsListComponent implements AfterViewInit, OnDestroy {
|
||||
vp.renderedRangeStream
|
||||
.pipe(debounceTime(300))
|
||||
.subscribe((range) => {
|
||||
const visibleChannels = this.channels().slice(
|
||||
const visibleChannels = this.sortedChannels().slice(
|
||||
range.start,
|
||||
range.end
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user