mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 12:36:15 -08:00
feat: general development, update core application components, and enhance build scripts.
This commit is contained in:
1 parent
9a57eaff92
commit
992b996ecd
37 files changed
+500
-341
No files matched your search
@@ -17,7 +17,6 @@ import {
|
||||
} from 'rxjs';
|
||||
import { DataService, EpgService, PlaylistsService } from 'services';
|
||||
import {
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
OPEN_MPV_PLAYER,
|
||||
OPEN_VLC_PLAYER,
|
||||
Playlist,
|
||||
@@ -25,10 +24,10 @@ import {
|
||||
VideoPlayer,
|
||||
} from 'shared-interfaces';
|
||||
import {
|
||||
PlaylistActions,
|
||||
ChannelActions,
|
||||
EpgActions,
|
||||
FavoritesActions,
|
||||
PlaylistActions,
|
||||
} from './actions';
|
||||
import {
|
||||
selectActive,
|
||||
@@ -57,10 +56,7 @@ export class PlaylistEffects {
|
||||
this.store.select(selectFavorites),
|
||||
this.store.select(selectActivePlaylistId)
|
||||
),
|
||||
filter(
|
||||
([, , playlistId]) =>
|
||||
playlistId !== GLOBAL_FAVORITES_PLAYLIST_ID
|
||||
),
|
||||
filter(([, , playlistId]) => !!playlistId),
|
||||
switchMap(([, favorites, playlistId]) =>
|
||||
this.playlistsService.updateFavorites(playlistId, favorites)
|
||||
),
|
||||
@@ -81,10 +77,7 @@ export class PlaylistEffects {
|
||||
return this.actions$.pipe(
|
||||
ofType(FavoritesActions.setFavorites),
|
||||
combineLatestWith(this.store.select(selectActivePlaylistId)),
|
||||
filter(
|
||||
([, playlistId]) =>
|
||||
playlistId !== GLOBAL_FAVORITES_PLAYLIST_ID
|
||||
),
|
||||
filter(([, playlistId]) => !!playlistId),
|
||||
switchMap(([action, playlistId]) =>
|
||||
this.playlistsService.setFavorites(
|
||||
playlistId,
|
||||
@@ -116,7 +109,8 @@ export class PlaylistEffects {
|
||||
activeChannel?.url +
|
||||
(activeChannel?.epgParams ?? ''),
|
||||
title: activeChannel?.name ?? '',
|
||||
'user-agent': activeChannel?.http?.['user-agent'],
|
||||
'user-agent':
|
||||
activeChannel?.http?.['user-agent'],
|
||||
referer: activeChannel?.http?.referrer,
|
||||
origin: activeChannel?.http?.origin,
|
||||
});
|
||||
@@ -130,7 +124,8 @@ export class PlaylistEffects {
|
||||
activeChannel?.url +
|
||||
(activeChannel?.epgParams ?? ''),
|
||||
title: activeChannel?.name ?? '',
|
||||
'user-agent': activeChannel?.http?.['user-agent'],
|
||||
'user-agent':
|
||||
activeChannel?.http?.['user-agent'],
|
||||
referer: activeChannel?.http?.referrer,
|
||||
origin: activeChannel?.http?.origin,
|
||||
});
|
||||
@@ -314,7 +309,10 @@ export class PlaylistEffects {
|
||||
type: 'xtream',
|
||||
});
|
||||
this.router.navigate(['/xtreams/', playlist._id]);
|
||||
} else if (playlist.macAddress && this.dataService.isElectron) {
|
||||
} else if (
|
||||
playlist.macAddress &&
|
||||
this.dataService.isElectron
|
||||
) {
|
||||
// Create Stalker playlist in SQLite
|
||||
await window.electron.dbCreatePlaylist({
|
||||
id: playlist._id.toString(),
|
||||
@@ -394,7 +392,9 @@ export class PlaylistEffects {
|
||||
// Also delete from SQLite if running in Electron
|
||||
if ((window as any).electron?.dbDeleteAllPlaylists) {
|
||||
try {
|
||||
await (window as any).electron.dbDeleteAllPlaylists();
|
||||
await (
|
||||
window as any
|
||||
).electron.dbDeleteAllPlaylists();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error deleting playlists from SQLite:',
|
||||
|
||||
@@ -12,14 +12,18 @@ export const epgReducers = [
|
||||
const epgParams = `?utc=${from}&lutc=${now}`;
|
||||
return {
|
||||
...state,
|
||||
active: { ...state.active, epgParams } as Channel,
|
||||
active: state.active
|
||||
? ({ ...state.active, epgParams } as Channel)
|
||||
: undefined,
|
||||
};
|
||||
}),
|
||||
on(
|
||||
EpgActions.resetActiveEpgProgram,
|
||||
(state): PlaylistState => ({
|
||||
...state,
|
||||
active: { ...state.active, epgParams: '' } as Channel,
|
||||
active: state.active
|
||||
? ({ ...state.active, epgParams: '' } as Channel)
|
||||
: undefined,
|
||||
})
|
||||
),
|
||||
on(
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { EntityState } from '@ngrx/entity';
|
||||
import { createFeatureSelector, createSelector, Selector } from '@ngrx/store';
|
||||
import {
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
Playlist,
|
||||
PlaylistMeta,
|
||||
} from 'shared-interfaces';
|
||||
import { Playlist, PlaylistMeta } from 'shared-interfaces';
|
||||
import * as fromPlaylistMetaState from './playlists.state';
|
||||
import * as fromPlaylistState from './reducers';
|
||||
import { selectRouteParam } from './router.selectors';
|
||||
@@ -80,9 +76,7 @@ export const selectPlaylistTitle = createSelector(
|
||||
fromPlaylistMetaState.getPlaylistMetaEntities,
|
||||
selectCurrentPlaylistId,
|
||||
(data) => {
|
||||
if (data.selectedId === GLOBAL_FAVORITES_PLAYLIST_ID) {
|
||||
return 'Global favorites';
|
||||
} else if (
|
||||
if (
|
||||
data.entities &&
|
||||
data.selectedId &&
|
||||
data.entities[data.selectedId]
|
||||
|
||||
@@ -13,7 +13,6 @@ import { combineLatest, map, switchMap } from 'rxjs';
|
||||
import {
|
||||
Channel,
|
||||
DbStores,
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
Playlist,
|
||||
PlaylistMeta,
|
||||
PlaylistUpdateState,
|
||||
@@ -44,7 +43,7 @@ export class PlaylistsService {
|
||||
}
|
||||
|
||||
getPlaylist(id: string) {
|
||||
if (id === GLOBAL_FAVORITES_PLAYLIST_ID) {
|
||||
if (id === 'global-favorites') {
|
||||
return this.getPlaylistWithGlobalFavorites();
|
||||
} else {
|
||||
return this.dbService.getByID<Playlist>(DbStores.Playlists, id);
|
||||
@@ -102,7 +101,10 @@ export class PlaylistsService {
|
||||
: {}),
|
||||
// Stalker portal optional fields
|
||||
...(updatedPlaylist.stalkerSerialNumber !== undefined
|
||||
? { stalkerSerialNumber: updatedPlaylist.stalkerSerialNumber }
|
||||
? {
|
||||
stalkerSerialNumber:
|
||||
updatedPlaylist.stalkerSerialNumber,
|
||||
}
|
||||
: {}),
|
||||
...(updatedPlaylist.stalkerDeviceId1 !== undefined
|
||||
? { stalkerDeviceId1: updatedPlaylist.stalkerDeviceId1 }
|
||||
@@ -111,10 +113,16 @@ export class PlaylistsService {
|
||||
? { stalkerDeviceId2: updatedPlaylist.stalkerDeviceId2 }
|
||||
: {}),
|
||||
...(updatedPlaylist.stalkerSignature1 !== undefined
|
||||
? { stalkerSignature1: updatedPlaylist.stalkerSignature1 }
|
||||
? {
|
||||
stalkerSignature1:
|
||||
updatedPlaylist.stalkerSignature1,
|
||||
}
|
||||
: {}),
|
||||
...(updatedPlaylist.stalkerSignature2 !== undefined
|
||||
? { stalkerSignature2: updatedPlaylist.stalkerSignature2 }
|
||||
? {
|
||||
stalkerSignature2:
|
||||
updatedPlaylist.stalkerSignature2,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
)
|
||||
@@ -220,29 +228,26 @@ export class PlaylistsService {
|
||||
switchMap((portal) =>
|
||||
this.dbService.update(DbStores.Playlists, {
|
||||
...portal,
|
||||
favorites: portal.favorites?.filter(
|
||||
(i) => {
|
||||
const expectedId = String(favoriteId);
|
||||
const streamId = String(
|
||||
(i as Partial<XtreamItem>).stream_id ?? ''
|
||||
);
|
||||
const seriesId = String(
|
||||
(i as Partial<XtreamSerieItem>).series_id ?? ''
|
||||
);
|
||||
const movieId = String(
|
||||
(i as Partial<{ movie_id: string }>).movie_id ??
|
||||
''
|
||||
);
|
||||
const itemId = String((i as any).id ?? '');
|
||||
favorites: portal.favorites?.filter((i) => {
|
||||
const expectedId = String(favoriteId);
|
||||
const streamId = String(
|
||||
(i as Partial<XtreamItem>).stream_id ?? ''
|
||||
);
|
||||
const seriesId = String(
|
||||
(i as Partial<XtreamSerieItem>).series_id ?? ''
|
||||
);
|
||||
const movieId = String(
|
||||
(i as Partial<{ movie_id: string }>).movie_id ?? ''
|
||||
);
|
||||
const itemId = String((i as any).id ?? '');
|
||||
|
||||
return (
|
||||
streamId !== expectedId &&
|
||||
seriesId !== expectedId &&
|
||||
movieId !== expectedId &&
|
||||
itemId !== expectedId
|
||||
);
|
||||
}
|
||||
),
|
||||
return (
|
||||
streamId !== expectedId &&
|
||||
seriesId !== expectedId &&
|
||||
movieId !== expectedId &&
|
||||
itemId !== expectedId
|
||||
);
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './lib/channel.interface';
|
||||
export * from './lib/channel.model';
|
||||
export * from './lib/constants';
|
||||
export * from './lib/epg-channel-with-programs.interface';
|
||||
export * from './lib/epg-channel.model';
|
||||
export * from './lib/epg-item.interface';
|
||||
@@ -29,9 +28,9 @@ export * from './lib/xtream-vod-details.interface';
|
||||
export * from './lib/xtream-vod-stream.interface';
|
||||
|
||||
// Stalker interfaces
|
||||
export * from './lib/stalker-vod-details.interface';
|
||||
export * from './lib/stalker-serial-details.interface';
|
||||
export * from './lib/stalker-vod-details.interface';
|
||||
|
||||
// Unified VOD details (discriminated union + adapters)
|
||||
export * from './lib/vod-details-item.interface';
|
||||
export * from './lib/vod-details-adapters';
|
||||
export * from './lib/vod-details-item.interface';
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Id of the channel with favorite channels aggregated from all added playlists
|
||||
*/
|
||||
export const GLOBAL_FAVORITES_PLAYLIST_ID = 'GLOBAL_FAVORITES';
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Channel,
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
ParsedPlaylist,
|
||||
ParsedPlaylistItem,
|
||||
Playlist,
|
||||
@@ -39,7 +38,7 @@ export function createFavoritesPlaylist(
|
||||
channels: Channel[]
|
||||
): Partial<Playlist> {
|
||||
return {
|
||||
_id: GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
_id: 'global-favorites',
|
||||
count: channels.length,
|
||||
playlist: {
|
||||
items: channels,
|
||||
|
||||
File renamed without changes.
+11
-6
@@ -1,7 +1,14 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.scroll-viewport {
|
||||
min-height: calc(100vh - 190px);
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
|
||||
@@ -26,9 +33,9 @@
|
||||
}
|
||||
|
||||
#all-channels {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(100vh - 167px);
|
||||
}
|
||||
|
||||
.channel-list {
|
||||
@@ -89,7 +96,5 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 599px) {
|
||||
.scroll-viewport {
|
||||
min-height: calc(50vh - 159px);
|
||||
}
|
||||
/* Mobile responsive tweaks if needed */
|
||||
}
|
||||
+11
-11
@@ -26,20 +26,20 @@ export interface EnrichedChannel extends Channel {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-all-channels-tab',
|
||||
templateUrl: './all-channels-tab.component.html',
|
||||
styleUrls: ['./all-channels-tab.component.scss'],
|
||||
selector: 'app-all-channels-view',
|
||||
templateUrl: './all-channels-view.component.html',
|
||||
styleUrls: ['./all-channels-view.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
ChannelListItemComponent,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
ScrollingModule,
|
||||
TranslatePipe
|
||||
],
|
||||
ChannelListItemComponent,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
ScrollingModule,
|
||||
TranslatePipe,
|
||||
],
|
||||
})
|
||||
export class AllChannelsTabComponent implements OnDestroy {
|
||||
export class AllChannelsViewComponent implements OnDestroy {
|
||||
/** All channels (will be filtered by search) */
|
||||
readonly channels = input.required<Channel[]>();
|
||||
|
||||
+31
-48
@@ -1,14 +1,16 @@
|
||||
@if (displayedChannels().length) {
|
||||
<mat-tab-group color="primary" backgroundColor="primary">
|
||||
<!-- All Channels Tab -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<mat-icon class="tab-icon">list</mat-icon>
|
||||
<div class="tab-title">
|
||||
{{ 'CHANNELS.ALL_CHANNELS' | translate }}
|
||||
</div>
|
||||
</ng-template>
|
||||
<app-all-channels-tab
|
||||
@if (viewTitle()) {
|
||||
<header class="context-header">
|
||||
<div class="context-header__top">
|
||||
<h2>{{ viewTitle() | translate }}</h2>
|
||||
</div>
|
||||
</header>
|
||||
<div class="context-divider"></div>
|
||||
}
|
||||
|
||||
@switch (activeView()) {
|
||||
@case ('all') {
|
||||
<app-all-channels-view
|
||||
[channels]="displayedChannels()"
|
||||
[channelEpgMap]="channelEpgMap()"
|
||||
[progressTick]="progressTick()"
|
||||
@@ -19,17 +21,9 @@
|
||||
(channelSelected)="onChannelSelected($event)"
|
||||
(favoriteToggled)="onFavoriteToggled($event)"
|
||||
/>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Groups Tab -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<mat-icon class="tab-icon">category</mat-icon>
|
||||
<div class="tab-title">
|
||||
{{ 'CHANNELS.GROUPS' | translate }}
|
||||
</div>
|
||||
</ng-template>
|
||||
<app-groups-tab
|
||||
}
|
||||
@case ('groups') {
|
||||
<app-groups-view
|
||||
[groupedChannels]="groupedChannels()"
|
||||
[channelEpgMap]="channelEpgMap()"
|
||||
[progressTick]="progressTick()"
|
||||
@@ -39,34 +33,23 @@
|
||||
(channelSelected)="onChannelSelected($event)"
|
||||
(favoriteToggled)="onFavoriteToggled($event)"
|
||||
/>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Favorites Tab -->
|
||||
@let showFavorites = (playlistId$ | async) !== 'GLOBAL_FAVORITES';
|
||||
@if (showFavorites) {
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<mat-icon class="tab-icon">star</mat-icon>
|
||||
<div class="tab-title">
|
||||
{{ 'CHANNELS.FAVORITES' | translate }}
|
||||
</div>
|
||||
</ng-template>
|
||||
@let favorites = favorites$ | async;
|
||||
@if (favorites) {
|
||||
<app-favorites-tab
|
||||
[favorites]="favorites"
|
||||
[channelEpgMap]="channelEpgMap()"
|
||||
[progressTick]="progressTick()"
|
||||
[shouldShowEpg]="shouldShowEpg()"
|
||||
[activeChannelUrl]="activeChannelUrl()"
|
||||
(channelSelected)="onChannelSelected($event)"
|
||||
(favoriteToggled)="onFavoriteToggled($event)"
|
||||
(favoritesReordered)="onFavoritesReordered($event)"
|
||||
/>
|
||||
}
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-tab-group>
|
||||
@case ('favorites') {
|
||||
@let favorites = favorites$ | async;
|
||||
@if (favorites) {
|
||||
<app-favorites-view
|
||||
[favorites]="favorites"
|
||||
[channelEpgMap]="channelEpgMap()"
|
||||
[progressTick]="progressTick()"
|
||||
[shouldShowEpg]="shouldShowEpg()"
|
||||
[activeChannelUrl]="activeChannelUrl()"
|
||||
(channelSelected)="onChannelSelected($event)"
|
||||
(favoriteToggled)="onFavoriteToggled($event)"
|
||||
(favoritesReordered)="onFavoritesReordered($event)"
|
||||
/>
|
||||
}
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
<div class="loading-container">
|
||||
<mat-icon class="loading-spinner">sync</mat-icon>
|
||||
|
||||
+32
-1
@@ -1,5 +1,6 @@
|
||||
:host {
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
container-type: inline-size;
|
||||
@@ -52,3 +53,33 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Header ───────────────────────────────────────────────────────────────────
|
||||
.context-header {
|
||||
padding: 12px 16px 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.context-header__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 0.94rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--mat-sys-on-surface);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Divider ──────────────────────────────────────────────────────────────────
|
||||
.context-divider {
|
||||
height: 1px;
|
||||
background: var(--mat-sys-outline-variant);
|
||||
margin: 0 0 6px;
|
||||
opacity: 0.6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
+37
-21
@@ -5,12 +5,14 @@ import {
|
||||
computed,
|
||||
inject,
|
||||
Input,
|
||||
input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
@@ -22,18 +24,12 @@ import {
|
||||
selectActivePlaylistId,
|
||||
selectFavorites,
|
||||
} from 'm3u-state';
|
||||
import { BehaviorSubject, combineLatest, map, skipWhile } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest, filter, map, skipWhile } from 'rxjs';
|
||||
import { EpgService } from 'services';
|
||||
import {
|
||||
Channel,
|
||||
EpgProgram,
|
||||
GLOBAL_FAVORITES_PLAYLIST_ID,
|
||||
Settings,
|
||||
STORE_KEY,
|
||||
} from 'shared-interfaces';
|
||||
import { AllChannelsTabComponent } from './all-channels-tab/all-channels-tab.component';
|
||||
import { FavoritesTabComponent } from './favorites-tab/favorites-tab.component';
|
||||
import { GroupsTabComponent } from './groups-tab/groups-tab.component';
|
||||
import { Channel, EpgProgram, Settings, STORE_KEY } from 'shared-interfaces';
|
||||
import { AllChannelsViewComponent } from './all-channels-view/all-channels-view.component';
|
||||
import { FavoritesViewComponent } from './favorites-view/favorites-view.component';
|
||||
import { GroupsViewComponent } from './groups-view/groups-view.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-channel-list-container',
|
||||
@@ -41,12 +37,11 @@ import { GroupsTabComponent } from './groups-tab/groups-tab.component';
|
||||
styleUrls: ['./channel-list-container.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
AllChannelsTabComponent,
|
||||
AllChannelsViewComponent,
|
||||
CommonModule,
|
||||
FavoritesTabComponent,
|
||||
GroupsTabComponent,
|
||||
FavoritesViewComponent,
|
||||
GroupsViewComponent,
|
||||
MatIconModule,
|
||||
MatTabsModule,
|
||||
TranslatePipe,
|
||||
],
|
||||
})
|
||||
@@ -54,6 +49,7 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy {
|
||||
private readonly epgService = inject(EpgService);
|
||||
private readonly storage = inject(StorageMap);
|
||||
private readonly store = inject(Store);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
/** Map of channel ID to current EPG program */
|
||||
readonly channelEpgMap = signal(new Map<string, EpgProgram | null>());
|
||||
@@ -73,6 +69,30 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy {
|
||||
/** Item size for virtual scroll - compact when no EPG */
|
||||
readonly itemSize = computed(() => (this.shouldShowEpg() ? 68 : 48));
|
||||
|
||||
/** Active view (all, groups, favorites) */
|
||||
readonly activeView = input<string>('all');
|
||||
|
||||
readonly currentUrl = toSignal(
|
||||
this.router.events.pipe(
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
map((event) => (event as NavigationEnd).urlAfterRedirects)
|
||||
),
|
||||
{ initialValue: this.router.url }
|
||||
);
|
||||
|
||||
readonly viewTitle = computed(() => {
|
||||
const view = this.activeView();
|
||||
const url = this.currentUrl();
|
||||
if (view === 'all') return 'CHANNELS.ALL_CHANNELS';
|
||||
if (view === 'groups') return 'CHANNELS.GROUPS';
|
||||
if (view === 'favorites') {
|
||||
return url.includes('/workspace/global-favorites')
|
||||
? 'HOME.PLAYLISTS.GLOBAL_FAVORITES'
|
||||
: 'CHANNELS.FAVORITES';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
/** Channels array */
|
||||
_channelList: Channel[] = [];
|
||||
private readonly channelListSignal = signal<Channel[]>([]);
|
||||
@@ -98,11 +118,7 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy {
|
||||
|
||||
/** Displayed channels - filters out unfavorited channels in global favorites view */
|
||||
readonly displayedChannels = computed(() => {
|
||||
const channels = this.channelListSignal();
|
||||
if (this.activePlaylistIdSignal() === GLOBAL_FAVORITES_PLAYLIST_ID) {
|
||||
return channels.filter((ch) => this.favoriteIds().has(ch.url));
|
||||
}
|
||||
return channels;
|
||||
return this.channelListSignal();
|
||||
});
|
||||
|
||||
/** Object with channels sorted by groups */
|
||||
|
||||
File renamed without changes.
+10
-1
@@ -1,6 +1,15 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#favorites-list {
|
||||
height: calc(100vh - 105px);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.channel-list {
|
||||
+5
-5
@@ -12,17 +12,17 @@ import {
|
||||
} from '@angular/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { Channel, EpgProgram } from 'shared-interfaces';
|
||||
import { EnrichedChannel } from '../all-channels-tab/all-channels-tab.component';
|
||||
import { EnrichedChannel } from '../all-channels-view/all-channels-view.component';
|
||||
import { ChannelListItemComponent } from '../channel-list-item/channel-list-item.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-tab',
|
||||
templateUrl: './favorites-tab.component.html',
|
||||
styleUrls: ['./favorites-tab.component.scss'],
|
||||
selector: 'app-favorites-view',
|
||||
templateUrl: './favorites-view.component.html',
|
||||
styleUrls: ['./favorites-view.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ChannelListItemComponent, DragDropModule, TranslatePipe],
|
||||
})
|
||||
export class FavoritesTabComponent {
|
||||
export class FavoritesViewComponent {
|
||||
/** Favorite channels */
|
||||
readonly favorites = input.required<Channel[]>();
|
||||
|
||||
File renamed without changes.
+10
-1
@@ -1,6 +1,15 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
#groups-list {
|
||||
height: calc(100vh - 105px);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.channel-list {
|
||||
+5
-5
@@ -16,13 +16,13 @@ import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { Channel, EpgProgram } from 'shared-interfaces';
|
||||
import { EnrichedChannel } from '../all-channels-tab/all-channels-tab.component';
|
||||
import { EnrichedChannel } from '../all-channels-view/all-channels-view.component';
|
||||
import { ChannelListItemComponent } from '../channel-list-item/channel-list-item.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-groups-tab',
|
||||
templateUrl: './groups-tab.component.html',
|
||||
styleUrls: ['./groups-tab.component.scss'],
|
||||
selector: 'app-groups-view',
|
||||
templateUrl: './groups-view.component.html',
|
||||
styleUrls: ['./groups-view.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
ChannelListItemComponent,
|
||||
@@ -33,7 +33,7 @@ import { ChannelListItemComponent } from '../channel-list-item/channel-list-item
|
||||
TranslatePipe,
|
||||
],
|
||||
})
|
||||
export class GroupsTabComponent implements AfterViewInit, OnDestroy {
|
||||
export class GroupsViewComponent implements AfterViewInit, OnDestroy {
|
||||
private readonly cdr = inject(ChangeDetectorRef);
|
||||
private readonly ngZone = inject(NgZone);
|
||||
|
||||
@@ -17,4 +17,7 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<app-channel-list-container [channelList]="channels()" />
|
||||
<app-channel-list-container
|
||||
[channelList]="channels()"
|
||||
[activeView]="activeView()"
|
||||
/>
|
||||
@@ -27,6 +27,7 @@ import { ChannelListContainerComponent } from './../../channel-list-container/ch
|
||||
export class SidebarComponent {
|
||||
readonly channels = input<Channel[]>([]);
|
||||
readonly showPlaylistHeader = input(true);
|
||||
readonly activeView = input<string>('all');
|
||||
|
||||
private readonly store = inject(Store);
|
||||
private readonly translate = inject(TranslateService);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<mat-icon>{{ isLeftDrawerOpened ? 'menu' : 'menu_open' }}</mat-icon>
|
||||
</button>
|
||||
@if ((playlistId$ | async) !== 'GLOBAL_FAVORITES') {
|
||||
<!-- @if (playlistId$ | async) {
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="updateFavoriteStatus(activeChannel)"
|
||||
@@ -21,7 +21,7 @@
|
||||
}}</mat-icon
|
||||
>
|
||||
</button>
|
||||
}
|
||||
} -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="infoOverlayClicked.emit()"
|
||||
@@ -31,13 +31,13 @@
|
||||
</button>
|
||||
{{ activeChannel?.name }}
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
<!-- <button
|
||||
mat-icon-button
|
||||
(click)="settingsClicked.emit()"
|
||||
[matTooltip]="'MENU.SETTINGS' | translate"
|
||||
>
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
</button> -->
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="multiEpgClicked.emit()"
|
||||
|
||||
@@ -8,10 +8,10 @@ import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
FavoritesActions,
|
||||
selectActivePlaylistId,
|
||||
selectFavorites,
|
||||
selectIsEpgAvailable,
|
||||
FavoritesActions,
|
||||
} from 'm3u-state';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
|
||||
|
||||
Reference in new issue
Block a user