refactor(xtream): move search field to navigation panel

This commit is contained in:
4gray committed 2023-07-15 19:15:33 +02:00
1 parent 580bf1cc8b
commit d57fd52ac5
12 files changed
+122 -115

No files matched your search

+7
View File
@@ -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);
}
}
+26
View File
@@ -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;
}
}
-43
View File
@@ -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,
})
);
}