mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(ui): add filter and sort menu to header and clean styles
Add a new mat-menu component for filtering and sorting playlists in the header area. The filter section iterates playlistTypes and allows toggling type selection with visual check state. The sort section adds options for name (asc/desc) and date added (newest/oldest) with active-state visuals and click handlers that call setSortOptions. A close button is also included. Remove unused/duplicate CSS rules from header styles: drop .filter- container and obsolete .dropdown-menu-header rules to avoid styling conflicts and reduce dead code. Adjust surrounding selector formatting.
This commit is contained in:
1 parent
c84b42284a
commit
a874db0b41
6 files changed
+357
-190
No files matched your search
@@ -0,0 +1,123 @@
|
||||
<mat-menu #filterSortMenu="matMenu" class="filter-sort-menu" xPosition="before">
|
||||
<!-- Filter section -->
|
||||
<section class="filter-section">
|
||||
<div class="menu-section-header">
|
||||
<mat-icon class="section-icon">filter_list</mat-icon>
|
||||
<span>{{ 'HOME.FILTER_BY_TYPE' | translate }}</span>
|
||||
</div>
|
||||
@for (type of playlistTypes; track type.id) {
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="togglePlaylistType(type); $event.stopPropagation()"
|
||||
[class.active-option]="type.checked"
|
||||
>
|
||||
<mat-icon>{{ type.icon }}</mat-icon>
|
||||
<span class="menu-item-text">{{
|
||||
type.translationKey | translate
|
||||
}}</span>
|
||||
<mat-icon
|
||||
class="check-icon"
|
||||
[class.check-visible]="type.checked"
|
||||
>check</mat-icon
|
||||
>
|
||||
</button>
|
||||
}
|
||||
</section>
|
||||
|
||||
<!-- Sort section -->
|
||||
<section class="sort-section">
|
||||
<div class="menu-section-header">
|
||||
<mat-icon class="section-icon">sort</mat-icon>
|
||||
<span>{{ 'HOME.SORT_BY' | translate }}</span>
|
||||
</div>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="
|
||||
setSortOptions(SortBy.NAME, SortOrder.ASC);
|
||||
$event.stopPropagation()
|
||||
"
|
||||
[class.active-option]="isSortActive(SortBy.NAME, SortOrder.ASC)"
|
||||
>
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
<span class="menu-item-text">{{
|
||||
'HOME.SORT_OPTIONS.NAME_ASC' | translate
|
||||
}}</span>
|
||||
<mat-icon
|
||||
class="check-icon"
|
||||
[class.check-visible]="isSortActive(SortBy.NAME, SortOrder.ASC)"
|
||||
>check</mat-icon
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="
|
||||
setSortOptions(SortBy.NAME, SortOrder.DESC);
|
||||
$event.stopPropagation()
|
||||
"
|
||||
[class.active-option]="isSortActive(SortBy.NAME, SortOrder.DESC)"
|
||||
>
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
<span class="menu-item-text">{{
|
||||
'HOME.SORT_OPTIONS.NAME_DESC' | translate
|
||||
}}</span>
|
||||
<mat-icon
|
||||
class="check-icon"
|
||||
[class.check-visible]="
|
||||
isSortActive(SortBy.NAME, SortOrder.DESC)
|
||||
"
|
||||
>check</mat-icon
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="
|
||||
setSortOptions(SortBy.DATE_ADDED, SortOrder.DESC);
|
||||
$event.stopPropagation()
|
||||
"
|
||||
[class.active-option]="
|
||||
isSortActive(SortBy.DATE_ADDED, SortOrder.DESC)
|
||||
"
|
||||
>
|
||||
<mat-icon>date_range</mat-icon>
|
||||
<span class="menu-item-text">{{
|
||||
'HOME.SORT_OPTIONS.NEWEST' | translate
|
||||
}}</span>
|
||||
<mat-icon
|
||||
class="check-icon"
|
||||
[class.check-visible]="
|
||||
isSortActive(SortBy.DATE_ADDED, SortOrder.DESC)
|
||||
"
|
||||
>check</mat-icon
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="
|
||||
setSortOptions(SortBy.DATE_ADDED, SortOrder.ASC);
|
||||
$event.stopPropagation()
|
||||
"
|
||||
[class.active-option]="
|
||||
isSortActive(SortBy.DATE_ADDED, SortOrder.ASC)
|
||||
"
|
||||
>
|
||||
<mat-icon>date_range</mat-icon>
|
||||
<span class="menu-item-text">{{
|
||||
'HOME.SORT_OPTIONS.OLDEST' | translate
|
||||
}}</span>
|
||||
<mat-icon
|
||||
class="check-icon"
|
||||
[class.check-visible]="
|
||||
isSortActive(SortBy.DATE_ADDED, SortOrder.ASC)
|
||||
"
|
||||
>check</mat-icon
|
||||
>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Close button -->
|
||||
<div class="menu-footer">
|
||||
<button mat-button (click)="closeMenu()">
|
||||
{{ 'CLOSE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
@@ -0,0 +1,90 @@
|
||||
// Combined Filter & Sort menu styles
|
||||
.filter-section,
|
||||
.sort-section {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.menu-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
|
||||
.section-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: rgba(139, 92, 246, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
// Menu item layout with right-aligned checkmark
|
||||
::ng-deep .filter-sort-menu {
|
||||
.mat-mdc-menu-item {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 12px !important;
|
||||
min-height: 40px !important;
|
||||
padding: 0 16px !important;
|
||||
|
||||
.mat-icon:first-child {
|
||||
margin-right: 0 !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-item-text {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
margin-left: auto !important;
|
||||
margin-right: 0 !important;
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&.check-visible {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active-option {
|
||||
background-color: rgba(139, 92, 246, 0.12) !important;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 60%;
|
||||
background: rgba(139, 92, 246, 0.8);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
font-size: 18px !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
color: rgba(139, 92, 246, 1) !important;
|
||||
}
|
||||
|
||||
// Menu footer
|
||||
.menu-footer {
|
||||
padding: 4px 8px 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
Component,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenu, MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FilterActions, selectActiveTypeFilters } from 'm3u-state';
|
||||
import { SortBy, SortOrder, SortService } from 'services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-filter-sort-menu',
|
||||
templateUrl: './filter-sort-menu.component.html',
|
||||
styleUrls: ['./filter-sort-menu.component.scss'],
|
||||
imports: [MatButtonModule, MatIconModule, MatMenuModule, TranslateModule],
|
||||
})
|
||||
export class FilterSortMenuComponent {
|
||||
private store = inject(Store);
|
||||
private sortService = inject(SortService);
|
||||
|
||||
readonly menu = viewChild.required<MatMenu>('filterSortMenu');
|
||||
readonly menuTrigger = input<MatMenuTrigger>();
|
||||
readonly filterChanged = output<void>();
|
||||
|
||||
playlistTypes = [
|
||||
{
|
||||
id: 'm3u',
|
||||
icon: 'playlist_play',
|
||||
translationKey: 'HOME.PLAYLIST_TYPES.M3U',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
id: 'xtream',
|
||||
icon: 'cloud',
|
||||
translationKey: 'HOME.PLAYLIST_TYPES.XTREAM',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
id: 'stalker',
|
||||
icon: 'router',
|
||||
translationKey: 'HOME.PLAYLIST_TYPES.STALKER',
|
||||
checked: true,
|
||||
},
|
||||
];
|
||||
|
||||
private readonly selectedTypeFilters = this.store.selectSignal(
|
||||
selectActiveTypeFilters
|
||||
);
|
||||
|
||||
readonly SortBy = SortBy;
|
||||
readonly SortOrder = SortOrder;
|
||||
private readonly currentSortOptions = toSignal(
|
||||
this.sortService.getSortOptions(),
|
||||
{
|
||||
requireSync: true,
|
||||
}
|
||||
);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
if (this.selectedTypeFilters) {
|
||||
this.playlistTypes = this.playlistTypes.map((type) => {
|
||||
type.checked = this.selectedTypeFilters().includes(type.id);
|
||||
return type;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
togglePlaylistType(type: {
|
||||
id: string;
|
||||
icon: string;
|
||||
translationKey: string;
|
||||
checked: boolean;
|
||||
}) {
|
||||
const currentlySelectedCount = this.playlistTypes.filter(
|
||||
(f) => f.checked
|
||||
).length;
|
||||
|
||||
// Prevent deselecting if it's the last selected option
|
||||
if (type.checked && currentlySelectedCount === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
type.checked = !type.checked;
|
||||
this.store.dispatch(
|
||||
FilterActions.setSelectedFilters({
|
||||
selectedFilters: this.playlistTypes
|
||||
.filter((f) => f.checked)
|
||||
.map((f) => f.id),
|
||||
})
|
||||
);
|
||||
this.filterChanged.emit();
|
||||
}
|
||||
|
||||
setSortOptions(by: SortBy, order: SortOrder): void {
|
||||
this.sortService.setSortOptions({ by, order });
|
||||
}
|
||||
|
||||
isSortActive(by: SortBy, order: SortOrder): boolean {
|
||||
const options = this.currentSortOptions();
|
||||
return options?.by === by && options?.order === order;
|
||||
}
|
||||
|
||||
get activeFiltersCount(): number {
|
||||
return this.playlistTypes.filter((type) => !type.checked).length;
|
||||
}
|
||||
|
||||
closeMenu(): void {
|
||||
this.menuTrigger()?.closeMenu();
|
||||
}
|
||||
}
|
||||
@@ -34,23 +34,23 @@
|
||||
<app-add-playlist-menu
|
||||
(playlistTypeSelected)="openAddPlaylistDialog($event)"
|
||||
/>
|
||||
<!-- Sort button -->
|
||||
<!-- Combined Filter & Sort button -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="sortMenu"
|
||||
[matTooltip]="'HOME.SORT_PLAYLISTS' | translate"
|
||||
data-test-id="sort-playlists"
|
||||
#filterMenuTrigger="matMenuTrigger"
|
||||
[matMenuTriggerFor]="filterSortMenuComponent().menu()"
|
||||
[matTooltip]="'HOME.FILTER_AND_SORT' | translate"
|
||||
[matBadge]="filterSortMenuComponent().activeFiltersCount"
|
||||
[matBadgeHidden]="
|
||||
filterSortMenuComponent().activeFiltersCount === 0
|
||||
"
|
||||
matBadgeSize="small"
|
||||
matBadgeColor="accent"
|
||||
data-test-id="filter-sort-playlists"
|
||||
>
|
||||
<mat-icon>sort</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="filterPlaylistMenu"
|
||||
[matTooltip]="'HOME.FILTER_BY_TYPE' | translate"
|
||||
data-test-id="filter-playlist-by-type"
|
||||
>
|
||||
<mat-icon>filter_alt</mat-icon>
|
||||
<mat-icon>tune</mat-icon>
|
||||
</button>
|
||||
<app-filter-sort-menu [menuTrigger]="filterMenuTrigger" />
|
||||
@if (isDesktop) {
|
||||
<button
|
||||
mat-icon-button
|
||||
@@ -120,80 +120,6 @@
|
||||
</button>
|
||||
</mat-menu>
|
||||
}
|
||||
<!-- Sort menu -->
|
||||
<mat-menu #sortMenu="matMenu">
|
||||
<div
|
||||
class="dropdown-menu-header"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
{{ 'HOME.SORT_BY' | translate }}
|
||||
</div>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="setSortOptions(SortBy.NAME, SortOrder.ASC)"
|
||||
[class.active-sort]="
|
||||
isSortActive(SortBy.NAME, SortOrder.ASC)
|
||||
"
|
||||
>
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
<span>{{ 'HOME.SORT_OPTIONS.NAME_ASC' | translate }}</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="setSortOptions(SortBy.NAME, SortOrder.DESC)"
|
||||
[class.active-sort]="
|
||||
isSortActive(SortBy.NAME, SortOrder.DESC)
|
||||
"
|
||||
>
|
||||
<mat-icon>sort_by_alpha</mat-icon>
|
||||
<span>{{ 'HOME.SORT_OPTIONS.NAME_DESC' | translate }}</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="setSortOptions(SortBy.DATE_ADDED, SortOrder.DESC)"
|
||||
[class.active-sort]="
|
||||
isSortActive(SortBy.DATE_ADDED, SortOrder.DESC)
|
||||
"
|
||||
>
|
||||
<mat-icon>date_range</mat-icon>
|
||||
<span>{{ 'HOME.SORT_OPTIONS.NEWEST' | translate }}</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="setSortOptions(SortBy.DATE_ADDED, SortOrder.ASC)"
|
||||
[class.active-sort]="
|
||||
isSortActive(SortBy.DATE_ADDED, SortOrder.ASC)
|
||||
"
|
||||
>
|
||||
<mat-icon>date_range</mat-icon>
|
||||
<span>{{ 'HOME.SORT_OPTIONS.OLDEST' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
<mat-menu #filterPlaylistMenu="matMenu">
|
||||
<section
|
||||
(click)="$event.stopPropagation()"
|
||||
class="filter-container"
|
||||
>
|
||||
<div
|
||||
class="dropdown-menu-header"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
{{ 'HOME.FILTER_BY_TYPE' | translate }}
|
||||
</div>
|
||||
@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>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -344,10 +344,6 @@
|
||||
app-region: no-drag; // Allow button interactions
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.add-playlist-btn {
|
||||
background-color: rgba(139, 92, 246, 0.12) !important;
|
||||
color: rgba(139, 92, 246, 1) !important;
|
||||
@@ -365,35 +361,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-header {
|
||||
padding: 0 16px;
|
||||
margin: 8px 0;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.active-sort {
|
||||
background-color: rgba(139, 92, 246, 0.12) !important;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 60%;
|
||||
background: rgba(139, 92, 246, 0.8);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
margin-left: 8px;
|
||||
// Badge styling for filter count
|
||||
::ng-deep .mat-badge-accent .mat-badge-content {
|
||||
background: rgba(139, 92, 246, 1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
// Refined Material button and icon overrides
|
||||
|
||||
@@ -2,30 +2,28 @@ import {
|
||||
Component,
|
||||
HostBinding,
|
||||
OnInit,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
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 { DataService, SortBy, SortOrder, SortService } from 'services';
|
||||
import { DataService } from 'services';
|
||||
//import { shell } from 'electron';
|
||||
import { AddPlaylistMenuComponent, PlaylistType } from 'components';
|
||||
import { selectActiveTypeFilters, setSelectedFilters } from 'm3u-state';
|
||||
import { HomeComponent } from '../../../home/home.component';
|
||||
import { AboutDialogComponent } from '../about-dialog/about-dialog.component';
|
||||
import { AddPlaylistDialogComponent } from '../add-playlist/add-playlist-dialog.component';
|
||||
import { FilterSortMenuComponent } from '../filter-sort-menu/filter-sort-menu.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
@@ -33,9 +31,10 @@ import { AddPlaylistDialogComponent } from '../add-playlist/add-playlist-dialog.
|
||||
styleUrls: ['./header.component.scss'],
|
||||
imports: [
|
||||
AddPlaylistMenuComponent,
|
||||
FilterSortMenuComponent,
|
||||
FormsModule,
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
@@ -53,12 +52,13 @@ export class HeaderComponent implements OnInit {
|
||||
private dialog = inject(MatDialog);
|
||||
private dataService = inject(DataService);
|
||||
private router = inject(Router);
|
||||
private store = inject(Store);
|
||||
private sortService = inject(SortService);
|
||||
|
||||
readonly addPlaylistMenuComponent = viewChild.required(
|
||||
AddPlaylistMenuComponent
|
||||
);
|
||||
readonly filterSortMenuComponent = viewChild.required(
|
||||
FilterSortMenuComponent
|
||||
);
|
||||
readonly isDesktop = !!window.electron;
|
||||
readonly title = input.required<string>();
|
||||
readonly subtitle = input.required<string>();
|
||||
@@ -66,47 +66,6 @@ export class HeaderComponent implements OnInit {
|
||||
|
||||
isHome = true;
|
||||
|
||||
playlistTypes = [
|
||||
{
|
||||
title: 'M3U (local, url, text)',
|
||||
id: 'm3u',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
title: 'Xtream',
|
||||
id: 'xtream',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
title: 'Stalker',
|
||||
id: 'stalker',
|
||||
checked: true,
|
||||
},
|
||||
];
|
||||
|
||||
private readonly selectedTypeFilters = this.store.selectSignal(
|
||||
selectActiveTypeFilters
|
||||
);
|
||||
|
||||
readonly SortBy = SortBy;
|
||||
readonly SortOrder = SortOrder;
|
||||
private currentSortOptions: { by: SortBy; order: SortOrder };
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
if (this.selectedTypeFilters) {
|
||||
this.playlistTypes = this.playlistTypes.map((type) => {
|
||||
type.checked = this.selectedTypeFilters().includes(type.id);
|
||||
return type;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.sortService.getSortOptions().subscribe((options) => {
|
||||
this.currentSortOptions = options;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.isHome =
|
||||
this.activatedRoute.snapshot.component.name === HomeComponent.name;
|
||||
@@ -154,27 +113,6 @@ export class HeaderComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
onPlaylistFilterChange() {
|
||||
this.store.dispatch(
|
||||
setSelectedFilters({
|
||||
selectedFilters: this.playlistTypes
|
||||
.filter((f) => f.checked)
|
||||
.map((f) => f.id),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setSortOptions(by: SortBy, order: SortOrder): void {
|
||||
this.sortService.setSortOptions({ by, order });
|
||||
}
|
||||
|
||||
isSortActive(by: SortBy, order: SortOrder): boolean {
|
||||
return (
|
||||
this.currentSortOptions?.by === by &&
|
||||
this.currentSortOptions?.order === order
|
||||
);
|
||||
}
|
||||
|
||||
onSearchQueryUpdate(query: string): void {
|
||||
this.searchQuery.emit(query);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user