mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
feat(xtream): add category search functionality and update selection state
This commit is contained in:
1 parent
bcdaff368a
commit
f752ba2b85
4 files changed
+101
-11
No files matched your search
@@ -24,6 +24,7 @@ export interface SelectionState {
|
||||
page: number;
|
||||
limit: number;
|
||||
contentSortMode: XtreamCategorySortMode;
|
||||
categorySearchTerm: string;
|
||||
isLoadingDetails: boolean;
|
||||
detailsError: string | null;
|
||||
}
|
||||
@@ -38,6 +39,7 @@ const initialSelectionState: SelectionState = {
|
||||
page: 0,
|
||||
limit: Number(localStorage.getItem('xtream-page-size') ?? 25),
|
||||
contentSortMode: 'date-desc',
|
||||
categorySearchTerm: '',
|
||||
isLoadingDetails: false,
|
||||
detailsError: null,
|
||||
};
|
||||
@@ -88,6 +90,18 @@ export function withSelection() {
|
||||
});
|
||||
};
|
||||
|
||||
const filterBySearchTerm = (items: any[], searchTerm: string): any[] => {
|
||||
const normalized = searchTerm.trim().toLocaleLowerCase();
|
||||
if (!normalized) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return items.filter((item: any) => {
|
||||
const title = (item.title ?? item.name ?? '').toString();
|
||||
return title.toLocaleLowerCase().includes(normalized);
|
||||
});
|
||||
};
|
||||
|
||||
// Memoized sorted content - only recalculates when content/type changes
|
||||
const sortedContent = computed(() => {
|
||||
const categoryType = store.selectedContentType();
|
||||
@@ -166,6 +180,7 @@ export function withSelection() {
|
||||
const categoryId = store.selectedCategoryId();
|
||||
const categoryType = store.selectedContentType();
|
||||
const sortMode = store.contentSortMode();
|
||||
const searchTerm = store.categorySearchTerm();
|
||||
|
||||
// Access parent store content (from withContent)
|
||||
const storeAny = store as any;
|
||||
@@ -183,6 +198,10 @@ export function withSelection() {
|
||||
Number(item.category_id) === categoryId
|
||||
);
|
||||
if (categoryType === 'vod' || categoryType === 'series') {
|
||||
filteredContent = filterBySearchTerm(
|
||||
filteredContent,
|
||||
searchTerm
|
||||
);
|
||||
filteredContent = sortByMode(
|
||||
filteredContent,
|
||||
sortMode,
|
||||
@@ -203,6 +222,7 @@ export function withSelection() {
|
||||
const categoryId = store.selectedCategoryId();
|
||||
const categoryType = store.selectedContentType();
|
||||
const sortMode = store.contentSortMode();
|
||||
const searchTerm = store.categorySearchTerm();
|
||||
|
||||
// Access parent store content (from withContent)
|
||||
const storeAny = store as any;
|
||||
@@ -222,7 +242,7 @@ export function withSelection() {
|
||||
);
|
||||
if (categoryType === 'vod' || categoryType === 'series') {
|
||||
return sortByMode(
|
||||
filteredContent,
|
||||
filterBySearchTerm(filteredContent, searchTerm),
|
||||
sortMode,
|
||||
categoryType
|
||||
);
|
||||
@@ -237,6 +257,7 @@ export function withSelection() {
|
||||
getTotalPages: computed(() => {
|
||||
const categoryId = store.selectedCategoryId();
|
||||
const categoryType = store.selectedContentType();
|
||||
const searchTerm = store.categorySearchTerm();
|
||||
|
||||
// Access parent store content (from withContent)
|
||||
const storeAny = store as any;
|
||||
@@ -249,10 +270,14 @@ export function withSelection() {
|
||||
|
||||
let totalItems = 0;
|
||||
if (categoryId) {
|
||||
totalItems = content.filter(
|
||||
let filtered = content.filter(
|
||||
(item: any) =>
|
||||
Number(item.category_id) === categoryId
|
||||
).length;
|
||||
);
|
||||
if (categoryType === 'vod' || categoryType === 'series') {
|
||||
filtered = filterBySearchTerm(filtered, searchTerm);
|
||||
}
|
||||
totalItems = filtered.length;
|
||||
} else {
|
||||
totalItems = content.length;
|
||||
}
|
||||
@@ -320,6 +345,7 @@ export function withSelection() {
|
||||
selectedContentType: type,
|
||||
selectedCategoryId: null,
|
||||
page: 0,
|
||||
categorySearchTerm: '',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -337,6 +363,7 @@ export function withSelection() {
|
||||
patchState(store, {
|
||||
selectedCategoryId: newCategoryId,
|
||||
page: 0,
|
||||
categorySearchTerm: '',
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -387,6 +414,16 @@ export function withSelection() {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set selected category search term
|
||||
*/
|
||||
setCategorySearchTerm(term: string): void {
|
||||
patchState(store, {
|
||||
categorySearchTerm: term,
|
||||
page: 0,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset selection state
|
||||
*/
|
||||
|
||||
@@ -42,14 +42,41 @@
|
||||
</h2>
|
||||
</div>
|
||||
@if (canShowSortMenu()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="xtreamSortMenu"
|
||||
[matTooltip]="'Sort: ' + contentSortLabel()"
|
||||
aria-label="Sort content"
|
||||
>
|
||||
<mat-icon>sort</mat-icon>
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<mat-form-field
|
||||
class="category-search"
|
||||
appearance="outline"
|
||||
subscriptSizing="dynamic"
|
||||
>
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
type="search"
|
||||
spellcheck="false"
|
||||
[ngModel]="categorySearchTerm()"
|
||||
(ngModelChange)="onCategorySearchChange($event)"
|
||||
placeholder="Search in category"
|
||||
/>
|
||||
@if (categorySearchTerm()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
aria-label="Clear search"
|
||||
(click)="clearCategorySearch()"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="xtreamSortMenu"
|
||||
[matTooltip]="'Sort: ' + contentSortLabel()"
|
||||
aria-label="Sort content"
|
||||
>
|
||||
<mat-icon>sort</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-menu #xtreamSortMenu="matMenu">
|
||||
<button mat-menu-item (click)="setContentSortMode('date-desc')">
|
||||
<span>Date Added (Latest First)</span>
|
||||
|
||||
@@ -53,6 +53,17 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.category-search {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.scrollable-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Component, computed, inject, OnInit } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||
@@ -24,8 +27,11 @@ const XTREAM_CATEGORY_SORT_STORAGE_KEY = 'xtream-category-sort-mode';
|
||||
styleUrls: ['./xtream-main-container.component.scss', './sidebar.scss'],
|
||||
imports: [
|
||||
CategoryViewComponent,
|
||||
FormsModule,
|
||||
MatFormFieldModule,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatInputModule,
|
||||
MatMenuModule,
|
||||
MatTooltipModule,
|
||||
PlaylistSwitcherComponent,
|
||||
@@ -46,6 +52,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
readonly currentPlaylist = this.xtreamStore.currentPlaylist;
|
||||
readonly selectedCategoryId = this.xtreamStore.selectedCategoryId;
|
||||
readonly contentSortMode = this.xtreamStore.contentSortMode;
|
||||
readonly categorySearchTerm = this.xtreamStore.categorySearchTerm;
|
||||
readonly canShowSortMenu = computed(() => {
|
||||
const hasCategorySelected = this.selectedCategoryId() != null;
|
||||
const type = this.xtreamStore.selectedContentType();
|
||||
@@ -165,4 +172,12 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
this.xtreamStore.setContentSortMode(mode);
|
||||
localStorage.setItem(XTREAM_CATEGORY_SORT_STORAGE_KEY, mode);
|
||||
}
|
||||
|
||||
onCategorySearchChange(term: string): void {
|
||||
this.xtreamStore.setCategorySearchTerm(term);
|
||||
}
|
||||
|
||||
clearCategorySearch(): void {
|
||||
this.xtreamStore.setCategorySearchTerm('');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user