mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
refactor(xtream): move search field to navigation panel
This commit is contained in:
1 parent
580bf1cc8b
commit
d57fd52ac5
12 files changed
+122
-115
No files matched your search
@@ -0,0 +1,7 @@
|
||||
import { XtreamCodeActions } from '../../../shared/xtream-code-actions';
|
||||
|
||||
export interface Breadcrumb {
|
||||
action: XtreamCodeActions;
|
||||
title: string;
|
||||
category_id?: string;
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
<ng-container *ngIf="items.length > 0; else noItems">
|
||||
<div class="search">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="Search"
|
||||
[(ngModel)]="searchText"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<mat-card
|
||||
*ngFor="
|
||||
let item of items | filterBy : searchText : 'name';
|
||||
let item of items | filterBy : searchPhrase() : 'name';
|
||||
trackBy: trackByFn
|
||||
"
|
||||
(click)="itemClicked.emit(item)"
|
||||
|
||||
@@ -3,19 +3,6 @@
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.search {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.search-input {
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { NgFor, NgIf } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { XtreamItem } from '../../../../shared/xtream-item.interface';
|
||||
import { FilterPipe } from '../../shared/pipes/filter.pipe';
|
||||
import { PortalStore } from '../portal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-category-content-view',
|
||||
@@ -24,7 +25,9 @@ export class CategoryContentViewComponent {
|
||||
@Input({ required: true }) items: XtreamItem[];
|
||||
@Output() itemClicked = new EventEmitter<any>();
|
||||
|
||||
searchText: string;
|
||||
portalStore = inject(PortalStore);
|
||||
|
||||
searchPhrase = this.portalStore.searchPhrase;
|
||||
|
||||
trackByFn(_index: number, item: XtreamItem) {
|
||||
return item.stream_id;
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
import { NgFor, NgIf } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { XtreamCategory } from '../../../../shared/xtream-category.interface';
|
||||
import { FilterPipe } from '../../shared/pipes/filter.pipe';
|
||||
import { PortalStore } from '../portal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-category-view',
|
||||
standalone: true,
|
||||
template: `
|
||||
<ng-container *ngIf="items?.length > 0; else noItems">
|
||||
<div class="search">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="Search"
|
||||
[(ngModel)]="searchText"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<mat-card
|
||||
class="category-item"
|
||||
*ngFor="
|
||||
let item of items
|
||||
| filterBy : searchText : 'category_name';
|
||||
| filterBy : searchText() : 'category_name';
|
||||
trackBy: trackByFn
|
||||
"
|
||||
(click)="categoryClicked.emit(item)"
|
||||
@@ -77,19 +70,6 @@ import { FilterPipe } from '../../shared/pipes/filter.pipe';
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.search-input {
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
}
|
||||
`,
|
||||
],
|
||||
imports: [
|
||||
@@ -106,7 +86,9 @@ export class CategoryViewComponent {
|
||||
|
||||
@Output() categoryClicked = new EventEmitter<XtreamCategory>();
|
||||
|
||||
searchText: string;
|
||||
portalStore = inject(PortalStore);
|
||||
|
||||
searchText = this.portalStore.searchPhrase;
|
||||
|
||||
trackByFn(_index: number, item: XtreamCategory) {
|
||||
return item.category_id;
|
||||
|
||||
@@ -26,10 +26,21 @@
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div id="breadcrumbs">
|
||||
<span
|
||||
*ngFor="let item of breadcrumbs"
|
||||
(click)="breadcrumbClicked.emit(item)"
|
||||
>{{ item.title }}</span
|
||||
>
|
||||
<div id="sub-panel">
|
||||
<div class="breadcrumbs">
|
||||
<span
|
||||
*ngFor="let item of breadcrumbs"
|
||||
(click)="processBreadcrumbClick(item)"
|
||||
>{{ item.title }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="search" *ngIf="searchVisible">
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="Search by name"
|
||||
type="search"
|
||||
[ngModel]="searchPhrase()"
|
||||
(ngModelChange)="setSearchText($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,21 +12,40 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#breadcrumbs {
|
||||
#sub-panel {
|
||||
background: black;
|
||||
border-bottom: 1px solid #333;
|
||||
padding: 10px;
|
||||
padding: 0 10px 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
.breadcrumbs {
|
||||
span {
|
||||
cursor: pointer;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: '>';
|
||||
margin: 0 10px;
|
||||
&:not(:last-child)::after {
|
||||
content: '>';
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
span:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
span:hover {
|
||||
text-decoration: underline;
|
||||
.search {
|
||||
text-align: center;
|
||||
|
||||
.search-input {
|
||||
padding: 8px 8px 8px 40px;
|
||||
width: 300px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #333;
|
||||
color: #000;
|
||||
background: #ccc url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' class='bi bi-search' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'%3E%3C/path%3E%3C/svg%3E") no-repeat 13px center;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { NgFor } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { NgFor, NgIf } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { Breadcrumb } from '../breadcrumb.interface';
|
||||
import { ContentType } from '../content-type.enum';
|
||||
import { Breadcrumb } from '../xtream-main-container.component';
|
||||
import { PortalStore } from '../portal.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navigation-bar',
|
||||
@@ -18,14 +20,28 @@ import { Breadcrumb } from '../xtream-main-container.component';
|
||||
MatButtonModule,
|
||||
RouterLink,
|
||||
NgFor,
|
||||
FormsModule,
|
||||
NgIf,
|
||||
],
|
||||
})
|
||||
export class NavigationBarComponent {
|
||||
@Input({ required: true }) breadcrumbs: Breadcrumb[];
|
||||
@Input({ required: true }) contentType: ContentType;
|
||||
@Input() searchVisible = true;
|
||||
|
||||
@Output() contentTypeChanged = new EventEmitter<ContentType>();
|
||||
@Output() breadcrumbClicked = new EventEmitter<Breadcrumb>();
|
||||
|
||||
ContentTypeEnum = ContentType;
|
||||
portalStore = inject(PortalStore);
|
||||
searchPhrase = this.portalStore.searchPhrase;
|
||||
|
||||
processBreadcrumbClick(item: Breadcrumb) {
|
||||
this.setSearchText('');
|
||||
this.breadcrumbClicked.emit(item);
|
||||
}
|
||||
|
||||
setSearchText(text: string) {
|
||||
this.portalStore.setSearchPhrase(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
|
||||
export interface PortalState {
|
||||
searchPhrase: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PortalStore extends ComponentStore<PortalState> {
|
||||
constructor() {
|
||||
super({
|
||||
searchPhrase: '',
|
||||
});
|
||||
}
|
||||
|
||||
// selectors
|
||||
readonly searchPhrase = this.selectSignal((state) => state.searchPhrase);
|
||||
|
||||
// reducers
|
||||
readonly setSearchPhrase = this.updater(
|
||||
(state, searchPhrase: string): PortalState => ({
|
||||
...state,
|
||||
searchPhrase,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
[contentType]="selectedContentType"
|
||||
(breadcrumbClicked)="breadcrumbClicked($event)"
|
||||
(contentTypeChanged)="changeContentType($event)"
|
||||
(searchTextChanged)="setSearchPhrase($event)"
|
||||
[searchVisible]="
|
||||
currentLayout === 'category' || currentLayout === 'category_content'
|
||||
"
|
||||
/>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -45,7 +45,9 @@ import {
|
||||
} from '../../../shared/xtream-serie-details.interface';
|
||||
import { Settings, VideoPlayer } from '../settings/settings.interface';
|
||||
import { STORE_KEY } from '../shared/enums/store-keys.enum';
|
||||
import { Breadcrumb } from './breadcrumb.interface';
|
||||
import { PlayerDialogComponent } from './player-dialog/player-dialog.component';
|
||||
import { PortalStore } from './portal.store';
|
||||
import { SerialDetailsComponent } from './serial-details/serial-details.component';
|
||||
|
||||
const ContentTypes = {
|
||||
@@ -68,12 +70,6 @@ const ContentTypes = {
|
||||
},
|
||||
};
|
||||
|
||||
export interface Breadcrumb {
|
||||
action: XtreamCodeActions;
|
||||
title: string;
|
||||
category_id?: string;
|
||||
}
|
||||
|
||||
type LayoutView =
|
||||
| 'category'
|
||||
| 'category_content'
|
||||
@@ -110,6 +106,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
dataService = inject(DataService);
|
||||
dialog = inject(MatDialog);
|
||||
ngZone = inject(NgZone);
|
||||
portalStore = inject(PortalStore);
|
||||
snackBar = inject(MatSnackBar);
|
||||
storage = inject(StorageMap);
|
||||
store = inject(Store);
|
||||
@@ -125,6 +122,7 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
this.storage.get(STORE_KEY.Settings)
|
||||
) as Signal<Settings>;
|
||||
isLoading = true;
|
||||
searchPhrase = this.portalStore.searchPhrase();
|
||||
|
||||
commandsList = [
|
||||
new IpcCommand(XTREAM_RESPONSE, (response: XtreamResponse) =>
|
||||
@@ -365,4 +363,9 @@ export class XtreamMainContainerComponent implements OnInit {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setSearchPhrase(searchPhrase: string) {
|
||||
console.log(searchPhrase);
|
||||
this.searchPhrase = searchPhrase;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ComponentStore } from '@ngrx/component-store';
|
||||
import { XtreamCategory } from '../../../shared/xtream-category.interface';
|
||||
|
||||
export interface XtreamState {
|
||||
liveStreams: any[];
|
||||
series: any[];
|
||||
vods: any[];
|
||||
vodCategories: XtreamCategory[];
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class XtreamStore extends ComponentStore<XtreamState> {
|
||||
constructor() {
|
||||
super({ liveStreams: [], series: [], vods: [], vodCategories: [] });
|
||||
}
|
||||
|
||||
readonly liveStreams = this.selectSignal((state) => state.liveStreams);
|
||||
readonly series = this.selectSignal((state) => state.series);
|
||||
readonly vods = this.selectSignal((state) => state.vods);
|
||||
readonly vodCategories = this.selectSignal((state) => state.vodCategories);
|
||||
|
||||
readonly setLiveStreams = this.updater(
|
||||
(state, liveStreams: any[]): XtreamState => ({
|
||||
...state,
|
||||
liveStreams,
|
||||
})
|
||||
);
|
||||
|
||||
readonly setSerials = this.updater(
|
||||
(state, series: any[]): XtreamState => ({
|
||||
...state,
|
||||
series,
|
||||
})
|
||||
);
|
||||
|
||||
readonly setVods = this.updater(
|
||||
(state, vods: any[]): XtreamState => ({
|
||||
...state,
|
||||
vods,
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user