feat(type-filter): filter playlists by type

This commit is contained in:
4gray committed 2024-03-02 14:19:38 +01:00
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),
})
);
}
}
+5
View File
@@ -126,3 +126,8 @@ export const setAdjacentChannelAsActive = createAction(
direction: 'next' | 'previous';
}>()
);
export const setSelectedFilters = createAction(
`${STORE_KEY} Set selected filters`,
props<{ selectedFilters: string[] }>()
);
+2
View File
@@ -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 {
+13 -1
View File
@@ -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) =>
+6
View File
@@ -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,