mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 17:36:15 -08:00
feat(type-filter): filter playlists by type
This commit is contained in:
1 parent
8d5148026e
commit
58d62a32ea
8 files changed
+131
-14
No files matched your search
@@ -19,6 +19,7 @@ import { Playlist } from '../../../../shared/playlist.interface';
|
||||
import { DataService } from '../../services/data.service';
|
||||
import * as PlaylistActions from '../../state/actions';
|
||||
import {
|
||||
selectActiveTypeFilters,
|
||||
selectAllPlaylistsMeta,
|
||||
selectPlaylistsLoadingFlag,
|
||||
} from '../../state/selectors';
|
||||
@@ -47,20 +48,43 @@ export class RecentPlaylistsComponent implements OnDestroy {
|
||||
playlists$ = combineLatest([
|
||||
this.store.select(selectAllPlaylistsMeta),
|
||||
this.searchQuery,
|
||||
// eslint-disable-next-line @ngrx/avoid-combining-selectors
|
||||
this.store.select(selectActiveTypeFilters),
|
||||
]).pipe(
|
||||
// eslint-disable-next-line @ngrx/avoid-mapping-selectors
|
||||
map(([playlists, searchQuery]) =>
|
||||
map(([playlists, searchQuery, filters]) =>
|
||||
playlists
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.filter((item) => {
|
||||
const isStalkerFilter =
|
||||
item.macAddress && filters.includes('stalker');
|
||||
const isXtreamFilter =
|
||||
item.username &&
|
||||
item.password &&
|
||||
item.serverUrl &&
|
||||
filters.includes('xtream');
|
||||
const isM3uFilter =
|
||||
!item.username &&
|
||||
!item.password &&
|
||||
!item.serverUrl &&
|
||||
!item.macAddress &&
|
||||
filters.includes('m3u');
|
||||
|
||||
return (
|
||||
(isStalkerFilter && filters.includes('stalker')) ||
|
||||
(isXtreamFilter && filters.includes('xtream')) ||
|
||||
(isM3uFilter && filters.includes('m3u'))
|
||||
);
|
||||
})
|
||||
.filter((item) =>
|
||||
item.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
.sort((a, b) => a.position - b.position)
|
||||
)
|
||||
);
|
||||
|
||||
allPlaylistsLoaded = this.store.selectSignal(selectPlaylistsLoadingFlag);
|
||||
|
||||
@Input() sidebarMode = false;
|
||||
@Output() playlistClicked = new EventEmitter<string>();
|
||||
|
||||
/** IPC Renderer commands list with callbacks */
|
||||
commandsList = [
|
||||
@@ -113,8 +137,6 @@ export class RecentPlaylistsComponent implements OnDestroy {
|
||||
isMigrationPossible = false;
|
||||
migrationMessage = '';
|
||||
|
||||
@Output() playlistClicked = new EventEmitter<string>();
|
||||
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
private dialogService: DialogService,
|
||||
|
||||
@@ -21,14 +21,15 @@
|
||||
data-test-id="filter-playlist-by-name"
|
||||
>
|
||||
<mat-icon>search</mat-icon>
|
||||
</button>
|
||||
</button> -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="filterPlaylistMenu"
|
||||
[matTooltip]="'HOME.FILTER_BY_TYPE' | translate"
|
||||
data-test-id="filter-playlist-by-type"
|
||||
>
|
||||
<mat-icon>filter_list</mat-icon>
|
||||
</button> -->
|
||||
</button>
|
||||
<button
|
||||
*ngIf="isElectron; else pwaMenu"
|
||||
mat-icon-button
|
||||
@@ -38,6 +39,24 @@
|
||||
>
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
|
||||
<mat-menu #filterPlaylistMenu="matMenu">
|
||||
<section (click)="$event.stopPropagation()" class="filter-container">
|
||||
<h4>{{ 'HOME.FILTER_BY_TYPE' | translate }}:</h4>
|
||||
@for (type of playlistTypes; track $index) {
|
||||
<p>
|
||||
<mat-checkbox
|
||||
(click)="$event.stopPropagation()"
|
||||
[checked]="true"
|
||||
(change)="onPlaylistFilterChange()"
|
||||
[(ngModel)]="type.checked"
|
||||
>{{ type.title }}</mat-checkbox
|
||||
>
|
||||
</p>
|
||||
}
|
||||
</section>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #addPlaylistMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.add-playlist-btn {
|
||||
background-color: #3f51b5;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { AsyncPipe, NgFor, NgIf } from '@angular/common';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit, effect } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NgxWhatsNewModule } from 'ngx-whats-new';
|
||||
import { HomeComponent } from '../../../home/home.component';
|
||||
import { DataService } from '../../../services/data.service';
|
||||
import { WhatsNewService } from '../../../services/whats-new.service';
|
||||
import { setSelectedFilters } from '../../../state/actions';
|
||||
import { selectActiveTypeFilters } from '../../../state/selectors';
|
||||
import { AboutDialogComponent } from '../about-dialog/about-dialog.component';
|
||||
import {
|
||||
AddPlaylistDialogComponent,
|
||||
@@ -25,14 +30,17 @@ import {
|
||||
styleUrls: ['./header.component.scss'],
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
NgIf,
|
||||
NgFor,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
MatDividerModule,
|
||||
MatTooltipModule,
|
||||
NgIf,
|
||||
NgFor,
|
||||
FormsModule,
|
||||
NgxWhatsNewModule,
|
||||
ReactiveFormsModule,
|
||||
TranslateModule,
|
||||
],
|
||||
})
|
||||
@@ -57,14 +65,43 @@ export class HeaderComponent implements OnInit {
|
||||
|
||||
isHome = true;
|
||||
|
||||
/** Creates an instance of HeaderComponent */
|
||||
playlistTypes = [
|
||||
{
|
||||
title: 'M3U (local, url, text)',
|
||||
id: 'm3u',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
title: 'Xtream',
|
||||
id: 'xtream',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
title: 'Stalker',
|
||||
id: 'stalker',
|
||||
checked: true,
|
||||
},
|
||||
];
|
||||
|
||||
selectedTypeFilters = this.store.selectSignal(selectActiveTypeFilters);
|
||||
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private dialog: MatDialog,
|
||||
private dataService: DataService,
|
||||
private router: Router,
|
||||
private store: Store,
|
||||
private whatsNewService: WhatsNewService
|
||||
) {}
|
||||
) {
|
||||
effect(() => {
|
||||
if (this.selectedTypeFilters) {
|
||||
this.playlistTypes = this.playlistTypes.map((type) => {
|
||||
type.checked = this.selectedTypeFilters().includes(type.id);
|
||||
return type;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.isHome =
|
||||
@@ -117,4 +154,14 @@ export class HeaderComponent implements OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onPlaylistFilterChange() {
|
||||
this.store.dispatch(
|
||||
setSelectedFilters({
|
||||
selectedFilters: this.playlistTypes
|
||||
.filter((f) => f.checked)
|
||||
.map((f) => f.id),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -126,3 +126,8 @@ export const setAdjacentChannelAsActive = createAction(
|
||||
direction: 'next' | 'previous';
|
||||
}>()
|
||||
);
|
||||
|
||||
export const setSelectedFilters = createAction(
|
||||
`${STORE_KEY} Set selected filters`,
|
||||
props<{ selectedFilters: string[] }>()
|
||||
);
|
||||
@@ -10,12 +10,14 @@ export const playlistsAdapter: EntityAdapter<PlaylistMeta> =
|
||||
export interface PlaylistMetaState extends EntityState<PlaylistMeta> {
|
||||
selectedId: string;
|
||||
allPlaylistsLoaded: boolean;
|
||||
selectedFilters: string[];
|
||||
}
|
||||
|
||||
export const initialPlaylistMetaState: PlaylistMetaState =
|
||||
playlistsAdapter.getInitialState({
|
||||
selectedId: '',
|
||||
allPlaylistsLoaded: false,
|
||||
selectedFilters: ['m3u', 'xtream', 'stalker'],
|
||||
});
|
||||
|
||||
export const {
|
||||
|
||||
@@ -228,7 +228,19 @@ export const playlistReducer = createReducer(
|
||||
...state,
|
||||
playlists: playlistsAdapter.removeAll(state.playlists),
|
||||
};
|
||||
})
|
||||
}),
|
||||
on(
|
||||
PlaylistActions.setSelectedFilters,
|
||||
(state, { selectedFilters }): PlaylistState => {
|
||||
return {
|
||||
...state,
|
||||
playlists: {
|
||||
...state.playlists,
|
||||
selectedFilters,
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const selectIsEpgAvailable = (state: PlaylistState) =>
|
||||
|
||||
@@ -44,6 +44,12 @@ export const selectAllPlaylistsMeta = createSelector(
|
||||
fromPlaylistMetaState.getAllPlaylistsMeta
|
||||
);
|
||||
|
||||
export const selectActiveTypeFilters = createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
(state) => state.selectedFilters
|
||||
);
|
||||
|
||||
export const selectPlaylistEntity = (id: string) =>
|
||||
createSelector(
|
||||
selectPlaylistsMetaState,
|
||||
|
||||
Reference in new issue
Block a user